画像アップロードをWeb公開から切り離す「四段ゲート」
画像アップロードを拡張子だけで判定し、public/uploadsへ保存している。これを直したいWeb開発責任者向けに、受付から配信までを分ける「四段ゲート」と回帰試験を示します。
根本原因は「検査不足」より「即時公開」
危険なファイル種別を制限せず受け取る問題はCWE-434に整理されています。利用者が申告するファイル名やMIMEタイプだけで描画方法を決めず、サーバー側でも検証する必要があります。[1]
よくある旧実装は、ブラウザのaccept属性と末尾の.jpgだけを見て、元の名前でWeb公開領域へ移します。これでは二重拡張子、MIMEの偽装、同名上書き、処理系による解釈差を一つの条件で防ごうとします。OWASPも、拡張子の許可リスト、内容検証、生成名、容量制限、Webルート外保存などを組み合わせる多層防御を勧めています。[2]
対象は、採用サイトの商品画像やプロフィール画像を扱う機能です。オーナーはWeb開発責任者、入力は許可形式、最大バイト数、最大画素数、閲覧権限、保存先、配信経路です。検証は必ず自社のlocal・test・staging環境で行います。
コピーして使う四段ゲート
image_upload_policy:
accepted_extensions: [jpg, jpeg, png]
accepted_media_types: [image/jpeg, image/png]
max_bytes: 5000000
max_pixels: 20000000
stored_name: uuid
storage: private://upload-quarantine
webroot_path: none
executable: false
output: decode_and_reencode
states: [quarantined, ready, rejected]
publish_when:
- server_side_checks == passed
- decode_and_reencode == succeeded
- storage_key_is_generated == true
- delivery_requires_handler == true
値は例です。最大容量と画素数は、実際に扱う画像、通信時間、画像処理メモリから決めます。元のファイル名は表示用メタデータにだけ残し、保存パスへ連結しません。
受付から配信までを順番に直す
- 受付で止める。 認証、権限、CSRF対策を確認し、読み込みながらバイト上限を超えた時点で中断します。拡張子は小文字化後の最終要素を許可リストと照合します。申告MIMEは早期拒否の参考にとどめます。
- 画像として作り直す。 ファイルシグネチャを確認したうえで、安全設定した画像ライブラリでデコードします。幅、高さ、総画素数を測り、許可したJPEGかPNGへ再エンコードします。シグネチャ単独では不十分であり、画像処理器自体にも資源制限と隔離が必要です。[2]
- 隔離して保存する。 アプリがUUIDと固定拡張子を生成し、Webルート外の隔離領域へ書きます。処理用アカウントには読み書きだけを与え、実行権限は持たせません。成功後に状態を
readyへ変えます。 - アプリ経由で配信する。
/media/{id}で閲覧権限と所有関係を確認し、サーバーが決めたContent-Typeで返します。Nginxを使う場合、保存領域をinternalにすると外部からの直接要求は404になり、認可後の内部要求だけを通せます。[3]
【仮想例】採用サイトが応募者の顔写真をpublic/uploads/{元名}へ保存していたとします。改修後は、受信直後にquarantinedを作成し、画像として再エンコードしてからUUID名で非公開領域へ置きます。応募者本人と採用担当だけが/media/{id}を通じて閲覧できます。完了条件は、許可画像が再エンコード済みであること、直接URLが404になること、未認可利用者が403になること、監査ログに判定理由が残ることです。
失敗モードと安全な展開
画像ライブラリへ無制限に入力すると、巨大な画素数や長い処理で資源を使い切ります。バイト数だけでなく、デコード後の画素数、処理時間、同時実行数を制限します。スキャンサービスへ送る場合は、個人情報を外部へ渡してよいかを先に確認します。[2]
移行は、まず旧アップロード領域の実行を無効化し、直接配信を止めます。次に新規分を四段ゲートへ通し、既存分は一覧化して再検査します。ロールバック時も公開ディレクトリ直書きへ戻さず、画像処理器の旧安定版へ戻して新規受付を隔離状態で保留します。
利用者がファイルを送らず、固定された社内画像だけを配信するという場合には向きません。PDFやOffice文書を扱う場合も、画像の再エンコードでは足りないため、形式別の検査とCDR、マクロ対策を別設計にします。
明日から使える回帰試験
| 入力・操作 | 合格条件 |
|---|---|
| 正常なJPEGとPNG | UUID名でready、元名はパスに使わない |
photo.jpg.php | 拡張子判定でrejected |
テキストを.jpgへ改名 | デコード失敗でrejected |
| 上限超過または巨大画素画像 | 公開せず、理由付きで停止 |
| 同じ元名を2回送信 | 別IDとなり、上書きしない |
| 保存領域へ直接アクセス | 404 |
未認可で/media/{id}へアクセス | 403、監査ログあり |
運用ではrejected_by_reason、デコード時間、隔離滞留数、直接パスへの要求数を監視します。合格は、拒否すべき入力が一件もreadyにならず、正常画像だけが認可経路から表示されることです。
参考資料
- MITRE CWE-434: Unrestricted Upload of File with Dangerous Type
- OWASP Cheat Sheet Series: File Upload Cheat Sheet
- NGINX Documentation: ngx_http_core_module — internal
本記事はAIを活用して作成し、公開前に自動レビューを実施しています。情報の確認日:2026-10-25。
Tác giả
ELYSAI編集部