テック技報最新号へ →
セキュリティ

画像アップロード API の入口対策チェックリスト(体積・形式・画素数・フレーム・再エンコード・保存名)

画像を受け取る API は「バイト数の上限 → 中身での形式判定 → デコード前の画素数チェック → フレーム数 → デコード失敗の扱い → 再エンコードでメタデータ除去 → サーバー側で決めた名前で非公開領域に保存」の順で守る。まず最初の 3 つから入れる。

@yousukezan · 2026-10-18
#upload#image#pillow#fastapi#decompression-bomb#exif#path-traversal#api

チェックリスト(失敗時の応答の例つき)

#確かめること想定する攻撃・事故応答
1本体のバイト数巨大ファイルでメモリ・ディスクを埋める413
2形式を中身で判定し許可リストで絞る拡張子偽装、想定外のデコーダーを叩かせる415
3幅×高さ(デコード前)decompression bomb(小さいファイルが展開後に巨大化)422
4フレーム数アニメーションで 1 フレーム分の検査をすり抜ける422
5デコード失敗壊れた・途中で切れたファイル400(500 にしない)
6再エンコードEXIF の GPS など位置情報の漏れ、紛れ込んだ余計なデータ保存時に処理
7ファイル名と保存先パストラバーサル、上書き、公開ディレクトリへの配置保存時に処理
8処理時間デコードに時間のかかる細工画像別プロセス+タイムアウト

各項目の要点

  1. 体積は実際に受け取ったバイト数で数える。Content-Length は chunked 転送では付かない。元記事では FastAPI のフォーム解析が上限超過の例外を飲み込んで 400 を返したため、ミドルウェアで応答を 413 に差し替える形にしている。前段のリバースプロキシでも同じ上限をかける。
  2. 拡張子と Content-Type は判断に使わない。Pillow なら Image.open(buf, formats=["JPEG", "PNG", "WEBP"]) で、先頭バイトから許可した形式のデコーダーだけを試す。SVG はスクリプトを埋め込めるので許可しない。
  3. 画素数は Image.open 直後の img.size で自分で比べる。open は画素のデコードを load() まで遅らせるので、この時点ではメモリを使っていない。Pillow の MAX_IMAGE_PIXELS は上限超えで警告、2 倍超えで初めて例外なので、警告の範囲は素通りする。warnings.simplefilter("error") はプロセス全体に効くので避ける。
  4. 単一画像の API なら n_frames > 1 をまとめて断る。アニメーションを受けるなら、フレーム数と総画素数の両方に上限を置く。
  5. 捕まえる例外を絞る(Pillow では OSError / ValueError / SyntaxError)。Exception で受けると自分のバグまで 400 になる。ログには中身ではなくサイズと検出形式だけ残す。
  6. ピクセルだけ新しい画像にコピーして保存し直すと、EXIF・ICC・テキストチャンクは持ち越されない。その前に ImageOps.exif_transpose で向きをピクセルに反映しないと縦写真が横倒しになる。JPEG の再圧縮劣化(Pillow 既定 quality 75)、16bit PNG の 8bit 化、色の見え方の変化というコストがある。
  7. 保存名はサーバーで作る(例: uuid4().hex +検出形式の拡張子)。元のファイル名を残したいなら DB の列に入れ、パスには使わない。保存先は静的配信のルートの外。配信時は検出形式の Content-Type と X-Content-Type-Options: nosniff を付ける。
  8. 時間の上限はスレッドでは切れない(asyncio.wait_for で待ってもスレッドは止まらない)。切るならデコードを別プロセスに出してタイムアウトで終了させる。

落とし穴

  • 「上限を実装した」と「全ルートで効いている」は別。元記事でも後から足したルートにだけ体積上限が効いていなかった。ルート一覧からテストを生成して全部に流すと防げる。
  • 再エンコードで polyglot ファイル(画像として正しいまま末尾に別データを足したもの)の多くは落ちるが、マルウェアスキャンの代わりにはならない。
  • HEIC / AVIF を受けるならプラグイン(pillow-heif など)とそのデコーダーの脆弱性に追従する必要がある。
  • 閾値や例外の種類は Pillow のバージョンで変わりうる(元記事は Pillow 13.0.0 で確認)。手元のバージョンで同じテストを流す。
  • 本人確認書類のような機微な画像は、そもそも保持期間を短くする。本人確認書類の画像は「確認後すぐ消す」「直リンクで見せない」 も参照。

出典

X の元の投稿を開く