アップロードされた元のファイル名の直接使用

アップロードされた元のファイル名の直接使用

説明

アップロードされた元のファイル名を保存先のパスやストレージ上の名前にそのまま使うと、../ などによるパス操作、意図しない上書き、実行可能な拡張子のファイルの保存につながるおそれがあります。元の名前は信頼せず、サーバー側で名前を生成するか、無害化してください。

想定される影響

  • アップロードディレクトリーの外にあるファイルが上書きされる可能性があります。
  • 既存のファイルや設定ファイルが破損するおそれがあります。
  • サーバーが公開するパスに悪意あるファイルが保存される可能性があります。

対処方法

  • クライアントが送った名前を、保存パスやストレージキーに直接使わないでください。
  • 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タイプと実際の内容も別途検証してください。

参考資料