취약점 개요(CWE-434)
파일을 웹 서버에 업로드 하는 취약점이다.
악성 파일을 웹에 업로드, 실행하여 시스템 장악 및 감염시키는 시나리오로 이루어진다. 알아야 할 것은 다음과 같다.
1. 어떤 파일 확장자가 업로드 가능한가?
php, exe, sh, py 등 실행 가능한 모든 파일들 업로드가 가능한지 확인하여야 한다. 보통 특정 확장자를 화이트리스트 방식으로 업로드 가능하게끔 논리를 구현한다.
2. 파일 시그니처 체크
파일 확장자는 어떤 프로그램으로 이 파일을 실행할 지에 대한 정의이다. 그러나 실질적으로는 파일 시그니처로 판단을 하며 실행을 한다. 따라서 확장자가 다르더라도 파일 시그니처가 php, py, sh 파일의 시그니처 일 시 실행이 가능하다.
3. MIME Type 체크
클라이언트에서 서버로 파일 업로드 시 MIME Type를 체크한다. 그러나 파일 확장자에서 컨펌이 나지 않으면, 이는 소용이 없기에 중요도는 낮다.
4. 업로드한 파일 실행이 가능한가?
파일 업로드 취약점과는 별개의 사항이다. 파일을 업로드 하더라도 이를 실행시키지 않으면 소용이 없다. 실제로는 담당 실무자가 파일을 실행하게끔 낚시 파일을 업로드 하고 기다릴 수 있고, LFI 또는 PHP Wrapper를 이용해 해당 리소스를 실행할 수도 있다.
공격 방법
fuxploider 툴 이용 (작성 중)
예상 피해
보안대책
파일 업로드 체크 논리 순서는 다음과 같다:
파일 확장자 -> MIME Type -> 파일 사이즈 -> 파일 시그니처
1. 업로드 가능한 파일 확장자를 화이트 리스트 방식으로 관리
2. MIME Type 체크
MIME Type 체크는 딱히 중요하지 않다. 정확히는 HTTP Request에 있는 Content type으로 하는 MIME Type 체크가 중요하지 않다.
이는 쉽게 변조가 가능하다. 따라서 magic.from_buffer 함수를 이용해 MIME Type을 서버 단에서 검사 후 체크하여야 한다.
3. 파일 사이즈 제한
4. 파일 시그니처 체크
5. 업로드 한 파일명 난수로 변경
6. 업로드한 파일 실행 권한 제거
7. WAF 도입
시큐어 코딩(파이썬)
안전하지 않은 코드
| from django.shortcuts import render from django.core.files.storage import FileSystemStorage def file_upload(request): if request.FILES['upload_file']: # 사용자가 업로드하는 파일을 검증 없이 저장하고 있어 안전하지 않다 upload_file = request.FILES['upload_file'] fs = FileSystemStorage(location='media/screenshot', base_url='media/screenshot') # 업로드 하는 파일에 대한 크기, 개수, 확장자 등을 검증하지 않음 filename = fs.save(upload_file.name, upload_file) return render(request, '/success.html', {'filename':filename} |
파일 업로드에 대한 아무런 검증이 없다.
안전한 코드
| import os import uuid import magic from django.shortcuts import render from django.core.files.storage import FileSystemStorage FILE_COUNT_LIMIT = 5 FILE_SIZE_LIMIT = 5 * 1024 * 1024 # 5MB WHITE_LIST_EXT = ['.jpg', '.jpeg'] ALLOWED_SIGNATURES = { '.jpg': [b'\xFF\xD8\xFF'], '.jpeg': [b'\xFF\xD8\xFF'] } def validate_mime_type(upload_file): """MIME 타입 검사""" mime = magic.from_buffer(upload_file.read(2048), mime=True) upload_file.seek(0) return mime in ['image/jpeg'] def check_file_signature(file, ext): """파일 시그니처 검사""" file.seek(0) file_header = file.read(3) file.seek(0) return any(file_header.startswith(sig) for sig in ALLOWED_SIGNATURES.get(ext, [])) def file_upload(request): """보안이 강화된 파일 업로드""" for filename, upload_file in request.FILES.items(): file_name, file_ext = os.path.splitext(upload_file.name) if file_ext.lower() not in WHITE_LIST_EXT: return render(request, '/error.html', {'error': '파일 확장자 오류'}) if not validate_mime_type(upload_file) or not check_file_signature(upload_file, file_ext): return render(request, '/error.html', {'error': '파일 검증 실패'}) return render(request, "/success.html") |
코드가 길어서 익숙치 않으면 읽기 어렵겠지만 막상 보면 간단하다.
1. def validate_mime_type
magic.from_buffer 로 2048 byte를 읽고 mime 타입을 체크
2. def check_file_signature
파일 시그니처를 가져온 후 사전 정의한 파일 시그니처와 대조
3. def file_upload
파일 확장자가 사전 정의된 확장자에 속하는지 확인
* 차후 확인 필요사항(개인용)
jpg 파일 내 php 또는 sh 코드 삽입 시 즉 스테가노 그라피를 어떻게 탐지할 것인가?
<?php, exec 같은 문자열들을 Hex로 전환하고 파일을 Hex 단위로 열람해 이를 검색함. (리소스... 파일이 겁나게 크면 그건 또...)
참고문헌
1. KISA 주요정보통신기반시설 기술적 취약점 분석 평가 방법 상세가이드
2. KISA Python_시큐어코딩_가이드(2023년_개정본)
'주요통신기반시설 취약점 가이드 > 주통기 웹' 카테고리의 다른 글
| XSLT 인젝션(Injection) (0) | 2025.11.26 |
|---|---|
| 쿠키 변조(Cookie Tampering) (0) | 2025.03.19 |
| 크로스 사이드 스크립트(XSS) (0) | 2025.03.18 |
| 경로 추적(Path Traversal) (0) | 2025.03.18 |
| 관리자 페이지 노출 (0) | 2025.02.20 |