PR 本記事には広告(Amazonアソシエイト・もしもアフィリエイト・A8.net等)が含まれます。掲載情報の正確性には努めていますが、商品の詳細は必ずリンク先で最新情報をご確認ください。
CTF・セキュリティ学習

【ファイルアップロード編】二重拡張子・MIME偽装・RCEの入り口|CTF思考フレームワーク #06

安全に生きたい編集部

📖 この記事はシリーズの一部です

「CTF思考フレームワーク」 Webフォーム編 #06 / 公開中 10記事 → シリーズ一覧を見る →

ファイルアップロード機能って、便利だけど怖いんですよね。プロフィール画像、添付資料、CSVインポート…受け取る側のサーバから見れば、「外部から実行可能ファイルが入ってくる入り口」になりかねない場所。今回はアップロードフォームを攻撃者目線で観察して、どこを狙われるか・どう守るかを整理していきます🗂️

CTFでも実務でも、ファイルアップロードは「一発でRCE(リモートコード実行)」につながる大物脆弱性が眠りがち。今日はそのチェックポイントを攻撃者と防御側の両方から見ていきましょう。

この記事の難易度

難易度:★★☆(やさしめ) / 所要:10〜12分 / 前提:#01〜#05の内容

👀 観察フェーズ:まず何を見る?

アップロードは 外部から実行可能ファイルが入る入口。観察ポイントが多いから慎重にね。😿

アップロードは外部から実行可能ファイルが入る入口。観察ポイントが多いから慎重にね。

[/jinr_fukidashi1]

アップロードフォームを見たら、まずは「サーバ側がどこまで信用しているか」を観察します。チェックポイントは6つ👇

アップロードフォームを見たら、まずは「サーバ側がどこまで信用しているか」を観察します。チェックポイントはこの6つ👇

accept属性って、クライアント側のチェック(ブラウザの飾り)がないと一発でやられます。サーバ側で必ず本命のチェックが必要です。🔍

accept属性、許可リストに頼ってる飾りなの…?信じてた💧

  • 受け付ける拡張子・MIME・最大サイズ
  • 保存先ディレクトリのパスとパーミッション
  • クライアント側だけで弾かれるバリデーション(緩すぎる証拠)
  • レスポンスに保存後の絶対URL/相対URLが返るか

🤔 仮説フェーズ:攻撃者は何を考える?

ファイルアップは RCE(Remote Code Execution=遠隔から任意コードを実行される最重大級の脆弱性)に直結する大物。だから、攻撃者も本気で来るよ。

ファイルアップはRCEに直結する大物だから、攻撃者も本気で来るよ。

攻撃者は 「許可リスト」「保存先パス」「実行可能性」の3軸でアップロード機能を分解します。代表的な4つの仮説を見ていきます。

攻撃者は「許可リスト」「保存先パス」「実行可能性」の3軸でアップロード機能を分解します。代表的な4つの仮説を見ていきます。

仮説①:拡張子チェックが ブラックリスト方式(NGリストに無いものは通す方式。リスト漏れに弱い)なら、.phtml.php5.phar など「リストから漏れている実行可能拡張子」が通るはず。

💭 仮説①:拡張子チェックがブラックリスト方式なら、.phtml .php5 .phar .shtml など「リストから漏れている実行可能拡張子」が通るはず。

仮説②:MIMEタイプ(ファイル種別を示すメタ情報)だけで判定していると、本物の画像にPHPコードを埋め込んだ ポリグロット(複数の形式として有効なファイル)が「JPEGとして」受け入れられ、保存先で実行されるはず。

💭 仮説②:MIMEだけで判定しているなら、本物の画像にPHPコードを埋め込んだポリグロットが「JPEGとして」受け入れられて、保存先で実行されるはず。

仮説③:保存先がWebルート配下なら、アップロード成功=直接URLでアクセス可能。実行権限が残っていればRCE成立。

こうやって 「許可リスト」「保存先パス」「実行可能性」の3軸で攻撃面を分解していくのが定石。

🎯 仮説①:拡張子チェックがブラックリスト方式

.php / .exe / .cgi だけ拒否していると、.phtml / .php5 / .shtml / .phar など 「リストから漏れた実行可能拡張子」が通る可能性。許可リスト方式(OKリストにあるものだけ通す)に切り替えるのが安全です。

え、.phpだけブロックしててもダメなの…?.phtmlとか初耳だよ💧

.phpだけブロックしててもダメなの…?💧

🖼️ 仮説②:MIMEタイプだけで判定している

クライアントが申告する Content-Type を信じている実装だと、本物の画像に PHPコードを埋め込んだポリグロットが「image/jpeg として」受理されます。Content-Typeはブラウザが自由に書き換えられるので、サーバ側でファイルの実体(マジックバイト)を見るのが正解。

画像に見せかけたPHPって、そんなことできるの…!?

うわ、画像に見せかけたPHPってそんなことできるの…!?

📁 仮説③:保存先がWebルート配下

アップロード成功=直接URLでアクセス可能で、実行可能ファイルが置かれた瞬間にRCE成立。Webルート外に置いてアプリ経由で配信する設計なら、URL直叩きを封じられます。

アップロード成功=直接URLでアクセス可能な構成だと、実行可能ファイルが置かれた瞬間にRCEが成立。Webルート外に置いてアプリ経由で配信するのが原則。

/uploads/ にそのまま保存されるサイト多いよね…あれ全部危ないの?!💧

/uploads/にそのまま保存されるサイト多いよね…あれ全部危ないの!?

↩️ 仮説④:ファイル名の正規化が抜けている

ファイル名に ../../var/www/html/ のような相対パス(..で1つ上の階層を指す表記)を含められると、保存先ディレクトリを飛び越えて任意のパスに書き込みされるパストラバーサル(パス横断)攻撃

ファイル名にも、入れるだけでこんなにも…ホラー💧

ファイル名に../入れるだけでどこにでも保存できちゃうの…ホラー💧

[/jinr_fukidashi2]

つまり 「許可リスト」「保存先パス」「実行可能性」「ファイル名正規化」の4軸で分解するのが定石。次は実際に検証する手順へ💡

🔬 検証フェーズ:どうやって確かめる?

アップロード検証は 画像→テキスト→実行ファイル の順で段階的に進めるのがコツ。📝

アップロード検証は画像→テキスト→実行ファイルの順で段階的に進めるのがコツ。

[/jinr_fukidashi1]

仮説を一つずつ確かめていきます。必ず自分の環境(自分が立てた検証用サーバ)か、明確に許可された場所でだけ実施してください。アップロード検証はRCE級の影響を出せるので、特に慎重に。

🔬 STEP 1:許可拡張子を炙り出す

まずは普通の画像をアップしてレスポンスを観察。次に .txt / .html / .svg / .php / .phtml / .jsp など順に試して、どのエラーメッセージが返るかで「拒否ロジック」を逆算します。エラー文の差分は攻撃者にとって”地図”になります。

🔬 STEP 2:保存先パスを特定する

アップロード成功後のレスポンスやHTMLのURL、画像表示パスから /static / /content / /files など、Webルート配下に直接置かれているなら危険サイン。直接URLでアクセスしてHTTPステータス(200なら直接読める/403なら制限あり)を確認します。

🔬 STEP 3:実行可能性をテストする

画像の末尾にPHPコードを埋めたポリグロットを送信し、保存後のURLを叩く。.phpx / .php3 / .php5 / .phar など実行可能な別拡張子も試す。Apacheの設定(AddHandler/AddType)次第では予期せぬ拡張子でPHPが走ります。

うわ、拡張子辞書を回すとかポリグロットとか…ツールであっさり試せちゃうの!?💧

そう。だから サーバ側で中身を見る・Webルート外に置く・実行権限なし3点が大事なんだ。

そう。だからサーバ側で中身を見て・Webルート外・実行権限なしの3点が大事なんだ。

⚔️ 攻撃フェーズ:実際の手口

“なぜ通るか”を理解するのがゴール。RCEに直結するから、本気で対策しよう。🚨

“なぜ通るか”を理解するのがゴール。RCEに直結するから本気で対策しよう。

[/jinr_fukidashi1]

実際の攻撃シナリオを3つ並べておきます。どれもサーバ側のバリデーション不足がトリガー。

🎯 シナリオ①:二重拡張子で実行ファイルを通す

実際の攻撃シナリオを並べておきます(あくまで防御のため)。どれも サーバ側のバリデーション不足がトリガーです。

shell.php.jpgshell.jpg.php のようにドットを2つ含めて投入。サーバが「最後のドット以降」だけ見ていると .php が通り、Webルートに置かれた瞬間 https://victim/uploads/shell.php.jpg でアクセスしてRCE。Apacheの古い AddHandler 設定では「途中に .php を含むファイル」も実行されがち。

🎯 シナリオ②:MIMEとマジックバイトを偽装した画像シェル

shell.php.jpgshell.jpg.php のようにドットを2つ含めて投入。サーバが「最後のドット以降」だけ見ていると .php が通り、Webルートに置かれた瞬間 https://victim/uploads/shell.phpWebshell(遠隔から任意コマンドが叩けるバックドア)になります。

本物のJPEGの先頭バイト(FF D8 FF)を維持したまま、ファイル末尾に <?php system($_GET["c"]); ?> を埋め込む(ポリグロット)。サーバはマジックバイトだけ見て「これはJPEGだ」と判定して通過。拡張子を .phtml.php5 に偽装すれば実行される構成も多い。Burp Suiteで Content-Type: image/jpeg を付けて投げるだけで通ることも。

🎯 シナリオ③:パストラバーサルで任意の場所に保存

本物のJPEGの先頭バイト FF D8 FF を維持したまま、ファイル末尾に <?php system($_GET["c"]); ?> を埋め込む(ポリグロット)。サーバはマジックバイトだけ見て「これはJPEGだ」と判定して通します。中身検証はgetimagesize()+再エンコードで無害化が鉄板。

CTF{upload_validation_must_be_server_side}

どれも共通するのは「サーバ側の最終バリデーションが甘い」という一点。クライアント側は飾りです。

ファイル名に ../../var/www/html/shell.php のような相対パスを混ぜて送信。サーバ側で basename() 相当の正規化(パスから危険な記号を除く処理)が抜けていると、保存先ディレクトリを飛び越して任意のパスに書き込まれる。.htaccess の上書きまでされたらサーバ全体が落ちます。

えっ、画像に偽装したPHPとかパストラバーサルでサーバ書き換えとか…ホラー過ぎるよ💧

どれも共通するのは サーバ側の最終バリデーションが甘いという点。クライアント側は飾りです。

shell.php.jpg のようにドット複数を含めて投入。サーバが「最後のドット以降」だけ見ていると .php が通り、Webshellとして実行可能になる。

画像に偽装したPHPって、サーバ書き換えとか…ホラー過ぎるよ💧

本物JPEGの先頭バイト(FF D8 FF)を残したままファイル末尾にPHPコードを埋め、Content-Typeも image/jpeg に偽装する。中身検証が緩いとそのまま保存される。

攻撃手口:パストラバーサルで任意保存

ファイル名に ../../var/www/html/shell.php を仕込む。basename()相当の正規化が抜けていると公開ディレクトリに直接Webshellが置かれる。

🛡️ 防御フェーズ:どう守る?

やっとお待ちかね、守る側の打ち手。アップロードは 3つの原則を守れば大体ふさげるよ。

守る側のチェックリストはこちら。「中身を見る」「Webルート外に置く」「実行権限を外す」の3点セットが基本。設計・実装・運用の3レイヤーで考える 多層防御の発想です。🛡️

なるほど…ファイル名はサーバ側で再生成(UUID等)→ ユーザー入力をパスに混ぜない、中身チェック・配置場所・実行権限の3点か。どれか1個でも守れていたら被害は防げるってことだね!

✅ ポイントは 「クライアントの申告を一切信じず、サーバ側で中身を見て、Webルート外に置く」3点セット。これが守れていれば大体のアップロード脆弱性は塞がります。

なるほど…中身チェック・配置場所・実行権限の3点か。どれか1個でも守れてたら被害は防げるってことだね!

その通り。多層防御がここでも効く。アップロードは特に RCEのリスクが高いから手厚くね。🛡️

許可拡張子のリストと、ファイル名の最後のドットの後ろで完全一致照合する。前段で Apache の multiviews(拡張子省略でも一致するファイルを返す機能)や PHP-FPMの曖昧な拡張子解釈を切っておくと、より堅牢。

許可拡張子のリストとファイル末尾の小文字拡張子を厳密一致で照合。前段でApacheの multiviews やPHP-FPMの曖昧な拡張子解釈を切る。

防御策:中身を必ずサーバ側で検証

getimagesize()やfile(1)で実体を確認。再エンコード(imagecreatefromjpegで読み直し→保存)すれば埋め込みPHPごと無害化できる。

getimagesize()file() でファイルの実体(中身)を確認。さらに再エンコード(imagecreatefromjpeg() で読み直し→保存)すれば、埋め込みPHPコードごと無害化できる。一番効果が高い対策。

防御策:実行不可ディレクトリに保存+名前再生成

アップロード先は php_admin_flag engine off。ファイル名はUUIDで再生成し、元ファイル名は使わない。

アップロード先は <Directory> php_admin_flag engine off(PHP実行禁止ディレクティブ)。ファイル名は UUID で再生成し、元ファイル名はDBに記録するだけでファイルシステムには載せない。

🛡️ 今日からできる対策ツール

パスワードの使い回しや手動管理はどんなに気をつけても限界があります。🔑 パスワード管理ツール「ワンパス」なら、複雑なパスワードを安全に保管して「1つのマスターパスワード」だけ覚えられるので、今日から始める防御策としてしっくりきます。

PR / 広告

ソースネクスト

※ 上記は他社サービスへのリンクです。購入は各自でご判断ください。

⚠️ よくある落とし穴

よくある実装ミスを順番に。

  1. 「クライアント側のaccept属性で安心」と思い込む。Burp等で簡単に書き換えられます。
  2. クライアント側の accept 属性で安心と思い込む ─ Burp Suite で簡単に書き換えられます。サーバ側の検証が必須
  3. 拡張子を「最後のドット以降」で取得して、二重拡張子を見逃す。
  4. 拡張子を「最後のドット以降」で取得して、二重拡張子を見逃す ─ shell.php.jpg が通るパターン。許可リスト+完全一致で防ぐ。
  5. Content-Typeヘッダだけで判定して、ファイルの中身を見ない。
  6. Content-Type ヘッダだけで判定して、ファイルの中身を見逃す ─ クライアントが自由に書ける値を信じてはいけない。マジックバイト+再エンコードで実体検証。
  7. 保存先をWebルート配下にして、直接URLで叩けてしまう ─ アップロード成功=即RCEの最悪パターン。配信はアプリ経由に。
  8. ファイル名にユーザー入力をそのまま使い、パストラバーサルを許す../ を含む名前で任意のパスに書き込まれる。UUIDで再生成するのが最も安全。
  9. ファイル名にユーザー入力をそのまま使い、パストラバーサルを許す。
  10. ImageMagick のバージョンが古く、ImageTragick系の脆弱性が残っている ─ 画像処理ライブラリ自体に脆弱性があるパターン(2016年のCVE-2016-3714 が有名)。依存ライブラリの定期アップデートを忘れずに。
  11. ImageMagickのバージョンが古く、ImageTragick系の脆弱性が残っている。

🧰 ツール早見表

CTFや診断でここで使うツールを早見表にまとめました。各ツールに“やさしい解説”を添えています。

ツール 用途 ひと言(やさしい解説)
Burp Suite リクエスト改ざん全般 送信内容を書き換えてサーバ反応を見る、Web診断の定番プロキシツール
file コマンド マジックバイト確認 ファイル先頭の数バイトから実体を判定する Linux標準コマンド
exiftool メタデータ抽出・操作 画像のEXIFやコメント領域を読み書き。ポリグロット作成にも使える
Upload Scanner(Burp拡張) 自動アップロード検査 多数のペイロードを一気に試して、抜けがないかを機械的にチェックできる

🎓 本気で学びたい人へ

Webセキュリティを「趣味」から「仕事」に変えたい方へ。🎓 ササエルはインフラエンジニアに特化したオンラインスクールで、セキュリティ設計の基礎から体系的に学べます。アップロード機能1つでも、設計→実装→運用の全体像で語れるようになると、現場での説得力がぐっと変わります。

PR / 広告

ササエル

📚 もっと深く学びたい人へ

体系的に攻撃と防御の両面を学びたいなら、『ホワイトハッカー入門 第2版』(IPUSIRON 著)が分かりやすい入口です。攻撃の仕組みを”手を動かしながら“理解する構成で、本記事の検証フェーズと相性抜群。📚

🤝 大事なお約束

必ず守ってね

この記事の手法は、必ず自分の環境か、許可された CTF・脆弱性報奨金プログラム(HackerOne, Bugcrowd 等。”見つけたら報告すれば報奨金がもらえる”公式ルートのこと)で試してください。他人のサービスに無断で攻撃を仕掛けるのは不正アクセス禁止法違反、立派な犯罪です。アップロード機能はとくにRCE級の被害に直結するので、慎重に。学んだ知識は守る側で活かすのが、この記事の唯一にして最大の約束です。🤝

📚 次に読みたい

📚 シリーズ全体の一覧を見る →

CTF・セキュリティ学習ハブページへ

#CTF#Web脆弱性#セキュリティ学習#思考フレームワーク#攻撃者視点
Recommend
こちらの記事もどうぞ
記事URLをコピーしました