
Reproduction Docker autonome et analyse de la CVE-2024-23897, la lecture arbitraire de fichiers dans la CLI Jenkins via l'expansion d'arguments à la syntaxe @ d'args4j.
Une reproduction autonome et entièrement locale de CVE-2024-23897, la lecture arbitraire de fichiers critique (score de base CVSS 3.1 de 9.8) dans la CLI Jenkins. Le projet déploie deux piles Docker qui ne diffèrent que par la version mineure de Jenkins, exécute le même proof of concept contre les deux, et montre la vulnérabilité se déclenchant sur le contrôleur non corrigé et restant silencieuse sur celui corrigé.
Tout est piloté par un unique programme Python, poc.py. Le harnais orchestre uniquement l'environnement (Docker Compose, le client officiel jenkins-cli.jar et la capture à l'identique de la sortie). La vulnérabilité elle-même réside dans le code Java de Jenkins et n'est jamais réimplémentée ici.
Une analyse écrite complète, incluant la revue du correctif et la décomposition CVSS, se trouve dans report/report.pdf.
La CLI Jenkins construit son analyseur d'arguments avec la bibliothèque args4j. args4j dispose d'une fonctionnalité appelée expandAtFiles, contrôlée par l'option et activée par défaut, qui réécrit tout argument de la forme en contenu de ce fichier avant l'exécution de la commande. Le fichier est ouvert avec les privilèges du processus du contrôleur Jenkins. Comme les trois transports de la CLI (HTTP, WebSocket, SSH) convergent vers le même analyseur, tout client capable d'envoyer une commande CLI peut lire des fichiers arbitraires sur le contrôleur. Le correctif (commit ) ajoute une constante dont la valeur par défaut est , désactivant ainsi l'expansion.
atSyntax@/path/to/file554f0378ALLOW_AT_SYNTAXfalseCe n'est pas un parcours de répertoire classique : il n'y a pas de ../ ni de répertoire de base à contourner. Le chemin est ouvert directement. Sur l'axe du résultat, il s'agit d'une lecture arbitraire de fichiers ; sur l'axe du mécanisme, d'une expansion d'arguments.
cve-2024-23897-jenkins-poc/
README.md # this file
LICENSE
poc.py # Python reproduction harness (all subcommands)
Dockerfile.vuln # jenkins/jenkins:2.426.2-lts + matrix-auth
Dockerfile.fix # jenkins/jenkins:2.426.3-lts + matrix-auth
docker-compose.vuln.yml # jenkins-vuln + attacker-vuln
docker-compose.fix.yml # jenkins-fix + attacker-fix
init.groovy.d/
01-create-users.groovy # bootstraps admin + readuser via matrix-auth
evidence/
docker-versions.txt # host Docker + Compose versions
output-vulnerable.txt # captured during `poc.py exploit`
output-fixed.txt # captured during `poc.py verify-fix`
report/
report.pdf # full written analysis
report.tex # LaTeX source (self-contained, no external figures)
| Exigence | Notes |
|---|---|
| Docker Engine 24 ou plus récent | la version exacte utilisée est consignée dans evidence/docker-versions.txt |
| Docker Compose v2 | fourni via le plugin intégré docker compose |
| Python 3.8 ou plus récent | bibliothèque standard uniquement, aucun pip install nécessaire |
| Espace disque | environ 1,5 Go pour deux images Jenkins, l'image temurin et le plugin matrix-auth |
Aucun JDK n'est requis sur l'hôte. Java s'exécute dans le conteneur de l'attaquant. Les fichiers Compose épinglent platform: linux/amd64 pour que les images se comportent à l'identique sur Apple Silicon ; c'est un choix opérationnel qui n'affecte pas la vulnérabilité, indépendante de la plateforme.
Depuis l'intérieur du dépôt :
python3 poc.py up-vuln # build and start the vulnerable stack, fetch the CLI jar
python3 poc.py place-proof # write the harmless marker file inside the controller
python3 poc.py exploit # run the PoC, writes evidence/output-vulnerable.txt
python3 poc.py up-fix # tear down vuln, build and start the patched stack
python3 poc.py verify-fix # run the same PoC, writes evidence/output-fixed.txt
python3 poc.py teardown # stop and remove both stacks
La première exécution prend quelques minutes (téléchargement des images plus installation du plugin). Les exécutions suivantes sont beaucoup plus rapides.
poc.py exploit réussit lorsque la chaîne de marquage POC-PROOF-LINE apparaît dans la sortie capturée (la fuite a eu lieu). poc.py verify-fix réussit dans la condition opposée : le marqueur doit être absent. Les deux sous-commandes se terminent avec un code de sortie non nul en cas d'échec, de sorte que les deux fichiers de preuve ainsi que leur statut de sortie constituent eux-mêmes le résultat du test.
Les deux fichiers Compose exécutent les deux mêmes services sur un réseau Docker : un contrôleur Jenkins (la victime) et un petit conteneur attaquant eclipse-temurin:17-jre. poc.py s'exécute sur l'hôte et pilote Docker via subprocess, mais l'invocation réelle java -jar jenkins-cli.jar ... s'exécute dans le conteneur de l'attaquant. L'attaquant n'a aucun accès au volume de données Jenkins ; il n'atteint le contrôleur que par le réseau, comme le ferait un attaquant distant.
init.groovy.d/01-create-users.groovy utilise le plugin matrix-auth pour créer deux comptes avec des permissions volontairement différentes :
| Compte | Permissions | Rôle dans le PoC |
|---|---|---|
admin | Jenkins.ADMINISTER | n'existe que pour satisfaire « au moins un administrateur », jamais utilisé pour attaquer |
readuser | Jenkins.READ uniquement (Overall/Read) | l'attaquant authentifié |
| anonymous | aucune | l'attaquant non authentifié |
Cela reproduit la répartition exacte de l'avis officiel, Overall/Read contre anonymous, plutôt que la répartition plus large « tout utilisateur connecté contre anonymous » qu'aurait produite la configuration par défaut du cœur de Jenkins. La fuite complète du fichier est donc imputable à la seule CVE, et non à une portée administrative.
Le PoC exécute quatre contextes contre chaque contrôleur. Le tableau ci-dessous résume la comparaison A/B ; les captures complètes se trouvent dans evidence/.
| Observation | Vulnérable 2.426.2 | Corrigé 2.426.3 |
|---|---|---|
traitement du jeton @ | développé en contenu du fichier | traité comme une chaîne littérale |
readuser + connect-node | divulgation complète du fichier (3 lignes sur 3) | aucune divulgation |
anonymous + who-am-i / help | fuite partielle (première ligne) via une erreur de l'analyseur avant le contrôle d'authentification | aucune divulgation |
marqueur POC-PROOF-LINE dans la sortie | présent | absent |
La seule variable qui change entre les deux colonnes est la version mineure de Jenkins, et à travers elle la valeur par défaut post-correctif de atSyntax. Les résultats opposés attribuent donc le changement de comportement au correctif de l'analyseur du commit 554f0378.
Exécuter Jenkins dans un conteneur n'est pas une atténuation. L'analyseur lit les fichiers avec les privilèges de la JVM Jenkins, et ces fichiers résident dans le même système de fichiers du conteneur qui héberge le magasin d'identifiants. La frontière du conteneur protège l'hôte du processus Jenkins, pas le processus Jenkins de lui-même. La configuration Docker présentée ici est un bac à sable de démonstration, rien de plus.
Passez à une version corrigée : 2.442 (hebdomadaire), ou 2.426.3 ou 2.440.1 (LTS), toutes publiées le 24 janvier 2024. Si une mise à niveau immédiate n'est pas possible, laissez la propriété système hudson.cli.CLICommand.allowAtSyntax non définie (sa valeur par défaut), ce qui maintient ALLOW_AT_SYNTAX à false et désactive l'expansion. Désactiver individuellement les transports de la CLI n'est qu'une mesure partielle, car les trois convergent vers le même analyseur.
Tout le travail s'exécute dans un environnement Docker local contre un fichier marqueur inoffensif de trois lignes (/tmp/poc-proof.txt) que le harnais crée lui-même. Aucune instance Jenkins publique n'est scannée ou contactée, et aucun secret réel tel que secrets/master.key ou credentials.xml n'est jamais lu.
554f0378 sur jenkinsci/jenkins : https://github.com/jenkinsci/jenkins/commit/554f03782057c499c49bbb06575f0d28b5200edbParserProperties.withAtSyntax) : https://github.com/kohsuke/args4j