ELYSAI
Development

画像アップロードをWeb公開から切り離す「四段ゲート」

ELYSAI編集部7分
画像アップロードを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

値は例です。最大容量と画素数は、実際に扱う画像、通信時間、画像処理メモリから決めます。元のファイル名は表示用メタデータにだけ残し、保存パスへ連結しません。

受付から配信までを順番に直す

  1. 受付で止める。 認証、権限、CSRF対策を確認し、読み込みながらバイト上限を超えた時点で中断します。拡張子は小文字化後の最終要素を許可リストと照合します。申告MIMEは早期拒否の参考にとどめます。
  2. 画像として作り直す。 ファイルシグネチャを確認したうえで、安全設定した画像ライブラリでデコードします。幅、高さ、総画素数を測り、許可したJPEGかPNGへ再エンコードします。シグネチャ単独では不十分であり、画像処理器自体にも資源制限と隔離が必要です。[2]
  3. 隔離して保存する。 アプリがUUIDと固定拡張子を生成し、Webルート外の隔離領域へ書きます。処理用アカウントには読み書きだけを与え、実行権限は持たせません。成功後に状態をreadyへ変えます。
  4. アプリ経由で配信する。 /media/{id}で閲覧権限と所有関係を確認し、サーバーが決めたContent-Typeで返します。Nginxを使う場合、保存領域をinternalにすると外部からの直接要求は404になり、認可後の内部要求だけを通せます。[3]

【仮想例】採用サイトが応募者の顔写真をpublic/uploads/{元名}へ保存していたとします。改修後は、受信直後にquarantinedを作成し、画像として再エンコードしてからUUID名で非公開領域へ置きます。応募者本人と採用担当だけが/media/{id}を通じて閲覧できます。完了条件は、許可画像が再エンコード済みであること、直接URLが404になること、未認可利用者が403になること、監査ログに判定理由が残ることです。

失敗モードと安全な展開

画像ライブラリへ無制限に入力すると、巨大な画素数や長い処理で資源を使い切ります。バイト数だけでなく、デコード後の画素数、処理時間、同時実行数を制限します。スキャンサービスへ送る場合は、個人情報を外部へ渡してよいかを先に確認します。[2]

移行は、まず旧アップロード領域の実行を無効化し、直接配信を止めます。次に新規分を四段ゲートへ通し、既存分は一覧化して再検査します。ロールバック時も公開ディレクトリ直書きへ戻さず、画像処理器の旧安定版へ戻して新規受付を隔離状態で保留します。

利用者がファイルを送らず、固定された社内画像だけを配信するという場合には向きません。PDFやOffice文書を扱う場合も、画像の再エンコードでは足りないため、形式別の検査とCDR、マクロ対策を別設計にします。

明日から使える回帰試験

入力・操作合格条件
正常なJPEGとPNGUUID名でready、元名はパスに使わない
photo.jpg.php拡張子判定でrejected
テキストを.jpgへ改名デコード失敗でrejected
上限超過または巨大画素画像公開せず、理由付きで停止
同じ元名を2回送信別IDとなり、上書きしない
保存領域へ直接アクセス404
未認可で/media/{id}へアクセス403、監査ログあり

運用ではrejected_by_reason、デコード時間、隔離滞留数、直接パスへの要求数を監視します。合格は、拒否すべき入力が一件もreadyにならず、正常画像だけが認可経路から表示されることです。

参考資料

  1. MITRE CWE-434: Unrestricted Upload of File with Dangerous Type
  2. OWASP Cheat Sheet Series: File Upload Cheat Sheet
  3. NGINX Documentation: ngx_http_core_module — internal

本記事はAIを活用して作成し、公開前に自動レビューを実施しています。情報の確認日:2026-10-25。

Chia sẻ bài viết

Tác giả

ELYSAI編集部

Trao đổi về bài toán AI của bạn

Trao đổi về bài toán AI của bạn

Bài viết liên quan