utsuroi file upload labs知識ベース 01 / 01
File Upload Vulnerabilities · Knowledge Base
01

File Upload の脆弱性

When a profile picture becomes a webshell

ユーザにファイルアップロードを許す機能(プロフィール画像、商品画像、添付資料)は便利ですが、検証が甘いと攻撃者が PHP / JSP 等の Web シェルをアップロードして実行できる事態に至ります。

1. なぜ Upload は危険か #what-is

アップロードされたファイルが Web 公開ディレクトリに置かれて、サーバ側で実行可能な拡張子(.php.jsp.aspx 等)で保存されると、攻撃者がそのファイルを HTTP で要求することで任意コードが実行されます。

// 攻撃者が webshell.php をアップロード
<?php system($_GET['cmd']); ?>

// /var/www/uploads/webshell.php に保存
// 攻撃者がアクセス: https://shop.utsuroi.example.com/uploads/webshell.php?cmd=cat /etc/passwd
// → サーバ上で任意コマンド実行

2. 拡張子チェック回避 #extension-tricks

3. Content-Type / MIME の偽装 #mime-type

クライアントが送る Content-Type ヘッダは攻撃者が自由に書ける。サーバ側でこれだけを見て検証するのは脆弱:

if ($_FILES['file']['type'] !== 'image/jpeg') abort(400);

Burp Suite で簡単に Content-Type: image/jpeg に書き換えられる。Content-Type は信用できない

4. マジックバイトの偽装 #magic-bytes

サーバが「ファイル先頭が JPEG マジックバイト(FF D8 FF)で始まるか」をチェックしている場合、攻撃者はJPEG ヘッダの後に PHP コードを連結する Polyglot ファイルを作る。

FF D8 FF E0 ... [JPEG header bytes] ...
<?php system($_GET['cmd']); ?>

これは file コマンドや getimagesize() でも JPEG として認識されることがあるが、.php として保存されれば PHP インタプリタが処理し、HTML/PHP 部分が実行される。

5. 正しい防御 #defense

  1. ファイル名は再生成 — 元のファイル名・拡張子を使わず、UUID + 検証済み拡張子で保存(例:{uuid}.jpg)
  2. ホワイトリスト拡張子 — 許可リストにある拡張子のみ。「.php を拒否」というブラックリストは漏れが必ず出る
  3. 実行権限を切る保存先 — アップロード先ディレクトリで PHP/CGI 実行を無効化(Apache の php_admin_value engine off や、CDN/オブジェクトストレージへの保存)
  4. 別ドメイン/別オリジン — アップロードファイルを uploads.utsuroi.example.com 等の別ドメインに置く。万一実行されても本体ドメインの Cookie 等が窃取されない
  5. マジックバイト検証 + 画像再エンコード — ImageMagick 等で再エンコード。これで PHP コードを末尾に付けた Polyglot ファイルでもコード部分が捨てられる
  6. Content-Type 検証は補助のみ — クライアント送信値は信用しない