画像アップロード API の入口対策チェックリスト(体積・形式・画素数・フレーム・再エンコード・保存名)
画像を受け取る API は「バイト数の上限 → 中身での形式判定 → デコード前の画素数チェック → フレーム数 → デコード失敗の扱い → 再エンコードでメタデータ除去 → サーバー側で決めた名前で非公開領域に保存」の順で守る。まず最初の 3 つから入れる。
チェックリスト(失敗時の応答の例つき)
| # | 確かめること | 想定する攻撃・事故 | 応答 |
|---|---|---|---|
| 1 | 本体のバイト数 | 巨大ファイルでメモリ・ディスクを埋める | 413 |
| 2 | 形式を中身で判定し許可リストで絞る | 拡張子偽装、想定外のデコーダーを叩かせる | 415 |
| 3 | 幅×高さ(デコード前) | decompression bomb(小さいファイルが展開後に巨大化) | 422 |
| 4 | フレーム数 | アニメーションで 1 フレーム分の検査をすり抜ける | 422 |
| 5 | デコード失敗 | 壊れた・途中で切れたファイル | 400(500 にしない) |
| 6 | 再エンコード | EXIF の GPS など位置情報の漏れ、紛れ込んだ余計なデータ | 保存時に処理 |
| 7 | ファイル名と保存先 | パストラバーサル、上書き、公開ディレクトリへの配置 | 保存時に処理 |
| 8 | 処理時間 | デコードに時間のかかる細工画像 | 別プロセス+タイムアウト |
各項目の要点
- 体積は実際に受け取ったバイト数で数える。
Content-Lengthは chunked 転送では付かない。元記事では FastAPI のフォーム解析が上限超過の例外を飲み込んで 400 を返したため、ミドルウェアで応答を 413 に差し替える形にしている。前段のリバースプロキシでも同じ上限をかける。 - 拡張子と
Content-Typeは判断に使わない。Pillow ならImage.open(buf, formats=["JPEG", "PNG", "WEBP"])で、先頭バイトから許可した形式のデコーダーだけを試す。SVG はスクリプトを埋め込めるので許可しない。 - 画素数は
Image.open直後のimg.sizeで自分で比べる。openは画素のデコードをload()まで遅らせるので、この時点ではメモリを使っていない。Pillow のMAX_IMAGE_PIXELSは上限超えで警告、2 倍超えで初めて例外なので、警告の範囲は素通りする。warnings.simplefilter("error")はプロセス全体に効くので避ける。 - 単一画像の API なら
n_frames > 1をまとめて断る。アニメーションを受けるなら、フレーム数と総画素数の両方に上限を置く。 - 捕まえる例外を絞る(Pillow では
OSError/ValueError/SyntaxError)。Exceptionで受けると自分のバグまで 400 になる。ログには中身ではなくサイズと検出形式だけ残す。 - ピクセルだけ新しい画像にコピーして保存し直すと、EXIF・ICC・テキストチャンクは持ち越されない。その前に
ImageOps.exif_transposeで向きをピクセルに反映しないと縦写真が横倒しになる。JPEG の再圧縮劣化(Pillow 既定 quality 75)、16bit PNG の 8bit 化、色の見え方の変化というコストがある。 - 保存名はサーバーで作る(例:
uuid4().hex+検出形式の拡張子)。元のファイル名を残したいなら DB の列に入れ、パスには使わない。保存先は静的配信のルートの外。配信時は検出形式のContent-TypeとX-Content-Type-Options: nosniffを付ける。 - 時間の上限はスレッドでは切れない(
asyncio.wait_forで待ってもスレッドは止まらない)。切るならデコードを別プロセスに出してタイムアウトで終了させる。
落とし穴
- 「上限を実装した」と「全ルートで効いている」は別。元記事でも後から足したルートにだけ体積上限が効いていなかった。ルート一覧からテストを生成して全部に流すと防げる。
- 再エンコードで polyglot ファイル(画像として正しいまま末尾に別データを足したもの)の多くは落ちるが、マルウェアスキャンの代わりにはならない。
- HEIC / AVIF を受けるならプラグイン(
pillow-heifなど)とそのデコーダーの脆弱性に追従する必要がある。 - 閾値や例外の種類は Pillow のバージョンで変わりうる(元記事は Pillow 13.0.0 で確認)。手元のバージョンで同じテストを流す。
- 本人確認書類のような機微な画像は、そもそも保持期間を短くする。本人確認書類の画像は「確認後すぐ消す」「直リンクで見せない」 も参照。
出典
- @yousukezan · 2026-10-17
- Qiita「画像アップロードAPIのセキュリティ対策を、実装レベルで振り返る」(@vaon-luandv): qiita.com
- 参考: OWASP File Upload Cheat Sheet cheatsheetseries.owasp.org