Ataque de Directory Traversal
Un ataque de directory traversal explota la validación insuficiente de rutas de archivos proporcionadas por el usuario para escapar del directorio previsto y acceder a ubicaciones arbitrarias en el sistema de archivos del servidor. Al inyectar secuencias de ruta relativa como ../ (dot-dot-slash), variantes codificadas o rutas absolutas, el atacante lee archivos confidenciales, descubre la estructura interna de la aplicación y — cuando se combina con primitivas de escritura — logra ejecución remota de código.
Cómo Funciona Ataque de Directory Traversal
Cada aplicación web confina sus operaciones de archivos a un directorio raíz designado — la raíz del documento para archivos estáticos, una carpeta de subidas para contenido de usuarios, o un directorio de plantillas para vistas. El directory traversal rompe este confinamiento. Cuando la aplicación construye una ruta del sistema de archivos usando entrada del usuario no saneada, el atacante inyecta secuencias que ascienden por el árbol de directorios hasta alcanzar la raíz del sistema, y luego desciende hacia cualquier directorio objetivo. La superficie de ataque es enorme: cualquier parámetro, cabecera, cookie o campo multipart que alimente una API del sistema de archivos es un punto de entrada potencial.
Mapear la superficie de ataque
El atacante identifica cada entrada que influye en operaciones del sistema de archivos: segmentos de ruta en URLs (/static/../../etc/passwd), parámetros de consulta (?file=report.pdf), campos del cuerpo POST, nombres de archivo en solicitudes multipart, valores de cookies y cabeceras HTTP (Content-Disposition, X-Forwarded-File). Escáneres automatizados como DirBuster, ffuf y Burp Intruder prueban sistemáticamente estos puntos de entrada. Los mensajes de error son invaluables — una excepción 'archivo no encontrado' que revela /var/www/app/pages/../../test confirma que la entrada llega al sistema de archivos y el traversal es interpretado.
Determinar la profundidad de traversal
El atacante calcula cuántas secuencias ../ se necesitan para alcanzar la raíz del sistema de archivos desde el directorio base de la aplicación. Una técnica común es solicitar secuencias de traversal progresivamente más profundas (../, ../../, ../../../, etc.) contra un archivo que se sabe que existe en la raíz — /etc/passwd en Linux o C:\Windows\win.ini en Windows. Una vez que el archivo es devuelto, la profundidad queda establecida. Usar más ../ de los necesarios es inofensivo: el sistema operativo simplemente permanece en la raíz.
Evadir filtros de entrada y reglas del WAF
Las aplicaciones modernas y los WAFs detectan secuencias básicas ../, por lo que los atacantes emplean un arsenal de técnicas de evasión: codificación URL (%2e%2e%2f), codificación URL doble (%252e%252e%252f), codificación UTF-8 extendida (..%c0%af), separadores mixtos de sistemas operativos (..\../ en Windows IIS), trucos de normalización de rutas (....// donde eliminar ../ una vez deja ../), inyección de byte nulo (%00 para truncar extensiones agregadas en runtimes antiguos) y exploits de normalización Unicode (..%u2216 usando caracteres de ancho completo). Cada técnica apunta a una capa diferente en la pila de análisis — el servidor web, el framework o el runtime del lenguaje.
Extraer objetivos de alto valor
Con una carga útil de traversal funcional, el atacante extrae archivos sistemáticamente en orden de prioridad: (1) /etc/passwd para enumerar usuarios del sistema; (2) archivos de configuración de la aplicación (.env, config.php, application.yml, appsettings.json) que contienen credenciales de bases de datos, claves API y secretos de cifrado; (3) /etc/shadow (si se ejecuta como root) para hashes de contraseñas; (4) claves privadas SSH (~/.ssh/id_rsa); (5) código fuente de la aplicación para auditar vulnerabilidades adicionales; (6) metadatos de la nube (AWS /proc/self/environ, /var/run/secrets/kubernetes.io para tokens de K8s); (7) archivos de log que contienen tokens de sesión y datos de usuarios.
Escalar a ejecución de código
Cuando el traversal se extiende a operaciones de escritura (inyección de logs, subida de archivos con manipulación de rutas o sobreescritura de plantillas), el atacante logra ejecución de código: sobreescribir una plantilla del lado del servidor con código malicioso, escribir una web shell PHP en la raíz del documento, inyectar contenido ejecutable en un archivo de tareas cron, o modificar .bashrc/.profile del usuario del servidor web. Incluso el traversal de solo lectura puede llevar al compromiso total cuando las credenciales extraídas (contraseñas de bases de datos, tokens API, claves SSH) proporcionan rutas de movimiento lateral hacia otros sistemas.
Ejemplos Reales
Apache HTTP Server 2.4.49/2.4.50 (CVE-2021-41773 y CVE-2021-42013)
Una falla crítica de directory traversal en Apache HTTP Server permitía a atacantes no autenticados mapear URLs a archivos fuera de la raíz del documento esperada usando secuencias dot-dot codificadas en URL (.%2e). La corrección inicial en 2.4.50 fue inmediatamente evadida mediante codificación doble (%%32%65), llevando a CVE-2021-42013. Cuando se combinaba con mod_cgi, la vulnerabilidad habilitaba ejecución remota de código. Los actores de amenazas comenzaron la explotación masiva en 24 horas, apuntando a un estimado de 112,000 servidores expuestos. CISA lo agregó al catálogo de Vulnerabilidades Explotadas Conocidas.
Fortinet FortiOS SSL VPN (CVE-2018-13379)
Un directory traversal en el portal web de la VPN SSL de Fortinet permitía a atacantes no autenticados leer archivos arbitrarios del sistema. Los atacantes extrajeron tokens de sesión VPN para secuestrar sesiones autenticadas. En noviembre de 2020, un actor de amenazas publicó una lista de casi 50,000 direcciones IP de VPN Fortinet vulnerables, y en septiembre de 2021, credenciales de 87,000 dispositivos fueron filtradas en un foro de la dark web. La vulnerabilidad fue explotada por grupos APT incluyendo APT29 (Cozy Bear) y fue citada en un aviso conjunto del FBI/CISA.
Citrix ADC/Gateway (CVE-2019-19781) — 'Shitrix'
Una vulnerabilidad de directory traversal en Citrix Application Delivery Controller y Gateway permitía ejecución remota de código sin autenticación. Los atacantes navegaban hasta un directorio de plantillas con permisos de escritura e implantaban un archivo XML malicioso que luego era renderizado por el motor de plantillas basado en Perl, logrando ejecución de comandos. Más de 80,000 organizaciones en 158 países eran vulnerables. La explotación comenzó el 10 de enero de 2020 — pocas semanas después de la publicación del PoC — y fue utilizada para desplegar ransomware (incluyendo REvil y DoppelPaymer) contra instituciones sanitarias, gubernamentales y financieras.
Impacto y Evaluación de Riesgo
El directory traversal se clasifica como Crítico porque colapsa la frontera de seguridad más fundamental en las aplicaciones web: el aislamiento del sistema de archivos. Un ataque exitoso proporciona acceso inmediato a archivos de configuración que contienen credenciales de bases de datos, secretos API y claves de cifrado. En cargas de trabajo alojadas en la nube, expone endpoints de metadatos de instancia y tokens de cuentas de servicio, habilitando la toma completa de la cuenta en la nube. Cuando el acceso de escritura es alcanzable, el camino hacia la ejecución remota de código es directo. Datos históricos de programas de divulgación de vulnerabilidades muestran que el directory traversal se clasifica consistentemente entre las 5 clases de vulnerabilidad más frecuentemente reportadas en programas de bug bounty. El ataque no requiere autenticación, no requiere herramientas especiales y frecuentemente evade las defensas perimetrales — haciéndolo accesible a atacantes de baja habilidad mientras sigue siendo devastador en impacto.
Cómo Detectar Ataque de Directory Traversal
Desplegar detección multicapa: (1) Reglas del WAF que coincidan con ../ y sus variantes codificadas (%2e%2e, %252e%252e, %c0%ae%c0%ae, ....//), bytes nulos (%00) y referencias a rutas sensibles conocidas (/etc/passwd, /etc/shadow, /proc/self, win.ini, web.config); (2) Registro a nivel de aplicación de todas las operaciones del sistema de archivos con la ruta canónica resuelta, alertando cuando cualquier ruta resuelta cae fuera del directorio base esperado; (3) Autoprotección de Aplicaciones en Tiempo de Ejecución (RASP) interceptando llamadas al sistema de E/S de archivos y bloqueando accesos fuera de límites en tiempo real; (4) Firmas de IDS/IPS a nivel de red para patrones de traversal en tráfico HTTP; (5) Monitoreo de integridad de archivos (AIDE, OSSEC) en archivos críticos del sistema para detectar lecturas no autorizadas mediante cambios en el tiempo de acceso. Correlacionar los intentos de traversal con la puntuación de inteligencia de amenazas de la IP solicitante para priorizar la respuesta a incidentes.
Cómo Prevenir Ataque de Directory Traversal
Adoptar una estrategia de defensa en profundidad: (1) Nunca construir rutas de archivos desde entrada del usuario — usar una lista de permitidos o un mapa de referencia indirecta (el usuario envía un ID, el servidor lo mapea a una ruta predefinida); (2) Si las rutas dinámicas son inevitables, resolver la ruta canónica (realpath() en C/PHP, Path.GetFullPath() en .NET, os.path.realpath() en Python, path.resolve() en Node.js) y verificar que comience con el directorio base esperado usando una verificación estricta de prefijo; (3) Rechazar cualquier entrada que contenga separadores de ruta (/, \), secuencias de traversal, bytes nulos o variantes codificadas en URL — hacer esto después de decodificar; (4) Usar aislamiento a nivel del sistema operativo: jaulas chroot, namespaces de Linux, contenedores Docker con sistemas de archivos raíz de solo lectura, o perfiles AppArmor/SELinux que restrinjan el acceso a archivos del proceso; (5) Aplicar el principio de mínimo privilegio — el proceso del servidor web solo debería poseer y acceder a su raíz del documento; (6) Desplegar un WAF con firmas de traversal completas, incluyendo variantes codificadas y Unicode; (7) En Windows IIS, deshabilitar la generación de nombres cortos (8.3) para prevenir traversal estilo PROGRA~1.
Ejemplos de Código
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
// VULNERABLE: user input directly concatenated into file path
app.get('/files/:name', (req, res) => {
const filePath = path.join(__dirname, 'uploads', req.params.name);
// No validation — attacker uses: /files/..%2f..%2f..%2fetc%2fpasswd
fs.readFile(filePath, (err, data) => {
if (err) return res.status(404).send('Not found');
res.send(data);
});
});
// Attacker requests:
// GET /files/....//....//....//etc/passwd
// GET /files/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
// GET /files/..%252f..%252f..%252fetc%252fpasswd (double encoding)
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
const UPLOAD_DIR = path.resolve(__dirname, 'uploads');
app.get('/files/:name', (req, res) => {
const fileName = req.params.name;
// 1. Reject any path separators or traversal sequences
if (/[\/\\]/.test(fileName) || fileName.includes('..')) {
return res.status(400).json({ error: 'Invalid filename' });
}
// 2. Resolve the canonical (real) path
const resolved = path.resolve(UPLOAD_DIR, fileName);
// 3. Strict prefix check — must be inside UPLOAD_DIR
if (!resolved.startsWith(UPLOAD_DIR + path.sep)) {
return res.status(403).json({ error: 'Access denied' });
}
// 4. Verify file exists and is a regular file (not symlink to outside)
fs.lstat(resolved, (err, stats) => {
if (err || !stats.isFile()) {
return res.status(404).json({ error: 'Not found' });
}
// 5. Check the real path of symlinks too
const real = fs.realpathSync(resolved);
if (!real.startsWith(UPLOAD_DIR + path.sep)) {
return res.status(403).json({ error: 'Access denied' });
}
res.sendFile(real);
});
});
package main
import (
"net/http"
"os"
"path/filepath"
"strings"
)
func safeFileHandler(baseDir string) http.HandlerFunc {
absBase, _ := filepath.Abs(baseDir)
return func(w http.ResponseWriter, r *http.Request) {
// Clean the requested path
reqPath := filepath.Clean(r.URL.Path)
// Resolve the full path
fullPath := filepath.Join(absBase, reqPath)
absPath, err := filepath.Abs(fullPath)
if err != nil {
http.Error(w, "Invalid path", http.StatusBadRequest)
return
}
// Verify the resolved path is within the base directory
if !strings.HasPrefix(absPath, absBase+string(os.PathSeparator)) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// Resolve symlinks and re-check
realPath, err := filepath.EvalSymlinks(absPath)
if err != nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
if !strings.HasPrefix(realPath, absBase+string(os.PathSeparator)) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
http.ServeFile(w, r, realPath)
}
}
func main() {
http.HandleFunc("/files/", safeFileHandler("./uploads"))
http.ListenAndServe(":8080", nil)
}
Preguntas Frecuentes
PowerWAF bloquea automáticamente Ataque de Directory Traversal antes de que llegue a tu servidor.
Implementa en minutos. Sin cambios de código. Plan gratuito disponible.
Los cupos del plan gratuito son limitados · No credit card required