업로드 파일의 원본 파일명 직접 사용

Unsafe Uploaded Filename Usage

설명

사용자가 보낸 업로드 파일의 원본 파일명을 그대로 저장 경로나 스토리지 이름으로 사용하면 ../ 같은 경로 조작, 의도치 않은 파일 덮어쓰기, 실행 가능한 확장자 저장으로 이어질 수 있습니다. 원본 파일명은 신뢰하지 말고 서버에서 안전한 이름을 다시 생성하거나 정제해야 합니다.

잠재적 영향

  • 업로드 디렉터리 밖의 파일을 덮어쓸 수 있습니다.
  • 기존 파일이나 설정 파일이 손상될 수 있습니다.
  • 서버가 제공하는 경로에 악성 파일이 저장될 수 있습니다.

해결 방법

  • 클라이언트가 보낸 원본 파일명을 저장 경로나 스토리지 키로 직접 사용하지 않습니다.
  • Flask/Werkzeug에서는 secure_filename()으로 파일명을 정제하거나 UUID 같은 서버 생성 이름을 사용합니다.
  • Django/FastAPI에서도 업로드 파일명 대신 UUID, 난수 토큰, 서버 정책 기반 이름을 생성해 저장합니다.
  • 저장 디렉터리와 파일 확장자는 허용 목록으로 제한합니다.

예시

변경 전

python
import os
from flask import request

def bad_upload():
    file = request.files["file"]
    file.save(os.path.join("uploads", file.filename))

변경 후

UPLOAD_DIR는 애플리케이션만 쓸 수 있는 비공개 디렉터리로 미리 준비하세요.

python
import uuid
from pathlib import Path
from flask import abort, request
from werkzeug.utils import secure_filename

UPLOAD_DIR = Path("/srv/app/upload-quarantine")
ALLOWED_EXTENSIONS = {".png", ".jpg", ".jpeg"}

def safe_upload():
    file = request.files["file"]
    original_name = secure_filename(file.filename or "")
    extension = Path(original_name).suffix.lower()
    if extension not in ALLOWED_EXTENSIONS:
        abort(400, description="Unsupported file extension")

    stored_name = f"{uuid.uuid4().hex}{extension}"
    file.save(UPLOAD_DIR / stored_name)
    return {"id": stored_name}, 201

설명:

  • 변경 전: 원본 파일명을 저장 경로에 그대로 결합합니다.
  • 변경 후: 원본 이름은 확장자 정책을 판단할 때만 정제해 사용하고, 허용된 확장자만 비공개 업로드 디렉터리에 저장합니다. 실제 저장 이름은 서버가 UUID로 생성하므로 원본 경로, 절대 경로, 덮어쓰기용 이름이 파일 시스템 경로에 반영되지 않습니다. 파일 형식이 보안 경계라면 별도로 MIME 타입과 실제 콘텐츠도 검증해야 합니다.

참조