
In sich geschlossene Docker-Reproduktion und -Analyse von CVE-2024-23897, dem beliebigen Dateilesen der Jenkins-CLI über die args4j @-Syntax-Argumenterweiterung.
Eine eigenständige, vollständig lokale Reproduktion von CVE-2024-23897, dem kritischen (CVSS 3.1 Base Score 9.8) beliebigen Dateizugriff in der Jenkins CLI. Das Projekt startet zwei Docker-Stacks, die sich nur durch die Jenkins-Minor-Version unterscheiden, führt denselben Proof-of-Concept gegen beide aus und zeigt, wie die Schwachstelle auf dem ungepatchten Controller ausgelöst wird und auf dem gepatchten verstummt.
Alles wird von einem einzigen Python-Programm gesteuert, poc.py. Die Testumgebung orchestriert lediglich die Umgebung (Docker Compose, den offiziellen jenkins-cli.jar-Client und die wörtliche Erfassung der Ausgabe). Die Schwachstelle selbst befindet sich im Java-Code von Jenkins und wird hier nie neu implementiert.
Eine vollständige schriftliche Analyse, einschließlich der Patch-Überprüfung und der CVSS-Aufschlüsselung, befindet sich in report/report.pdf.
Die Jenkins CLI erstellt ihren Argument-Parser mit der args4j-Bibliothek. args4j hat eine Funktion namens expandAtFiles, die durch das Flag atSyntax gesteuert wird und standardmäßig aktiviert ist. Diese Funktion ersetzt jedes Argument der Form @/pfad/zur/datei durch den Inhalt dieser Datei, bevor der Befehl ausgeführt wird. Die Datei wird mit den Berechtigungen des Jenkins-Controller-Prozesses geöffnet. Da alle drei CLI-Transporte (HTTP, WebSocket, SSH) in denselben Parser münden, kann jeder Client, der einen CLI-Befehl senden kann, beliebige Dateien vom Controller lesen. Der Fix (Commit 554f0378) fügt eine Konstante ALLOW_AT_SYNTAX hinzu, die standardmäßig auf false gesetzt ist und die Expansion deaktiviert.
Dies ist kein klassischer Path-Traversal: Es gibt kein ../ und kein Basisverzeichnis, aus dem man ausbrechen könnte. Der Pfad wird direkt geöffnet. Auf der Ergebnisebene handelt es sich um einen beliebigen Dateizugriff; auf der Mechanismenebene um eine Argument-Expansion.
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)
| Anforderung | Hinweise |
|---|---|
| Docker Engine 24 oder neuer | Die genaue verwendete Version ist in evidence/docker-versions.txt aufgezeichnet |
| Docker Compose v2 | wird als eingebautes docker compose-Plugin ausgeliefert |
| Python 3.8 oder neuer | nur Standardbibliothek, kein pip install erforderlich |
| Speicherplatz | etwa 1,5 GB für zwei Jenkins-Images, das Temurin-Image und das matrix-auth-Plugin |
Auf dem Host wird kein JDK benötigt. Java läuft im Angreifer-Container. Die Compose-Dateien legen platform: linux/amd64 fest, damit sich die Images auf Apple Silicon identisch verhalten; dies ist eine betriebliche Entscheidung und berührt nicht die Schwachstelle, die plattformunabhängig ist.
Von innerhalb des Repositorys:
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
Der erste Durchlauf dauert einige Minuten (Image-Pulls plus Plugin-Installation). Nachfolgende Durchläufe sind deutlich schneller.
poc.py exploit ist erfolgreich, wenn die Markierungszeichenfolge POC-PROOF-LINE in der erfassten Ausgabe erscheint (der Leak ist passiert). poc.py verify-fix ist beim gegenteiligen Zustand erfolgreich: Die Markierung muss fehlen. Beide Unterbefehle beenden sich mit einem Fehlercode ungleich Null bei Misserfolg, sodass die beiden Beweisdaten-Dateien zusammen mit ihrem Exit-Status selbst das Testergebnis darstellen.
Beide Compose-Dateien führen die gleichen zwei Dienste in einem Docker-Netzwerk aus: einen Jenkins-Controller (das Opfer) und einen kleinen eclipse-temurin:17-jre-Angreifer-Container. poc.py läuft auf dem Host und steuert Docker über subprocess, aber der eigentliche Aufruf von java -jar jenkins-cli.jar ... läuft innerhalb des Angreifer-Containers. Der Angreifer hat keinen Zugriff auf das Jenkins-Datenvolumen; er erreicht den Controller nur über das Netzwerk, so wie es ein entfernter Angreifer tun würde.
init.groovy.d/01-create-users.groovy verwendet das Matrix-Auth-Plugin, um zwei Konten mit bewusst unterschiedlichen Berechtigungen zu erstellen:
| Konto | Berechtigungen | Rolle im PoC |
|---|---|---|
admin | Jenkins.ADMINISTER | existiert nur, um "mindestens ein Admin" zu erfüllen, wird nie zum Angriff verwendet |
readuser | nur Jenkins.READ (Overall/Read) | der authentifizierte Angreifer |
| anonymous | keine | der nicht authentifizierte Angreifer |
Dies reproduziert die genaue Aufteilung aus dem offiziellen Advisory, Overall/Read versus anonymous, anstatt der lockereren "jeder angemeldete Benutzer versus anonymous", die der Jenkins-Kernstandard hervorgebracht hätte. Der vollständige Datei-Leak ist daher allein der CVE zuzuschreiben, nicht der administrativen Reichweite.
Der PoC führt vier Kontexte gegen jeden Controller aus. Die folgende Tabelle fasst den A/B-Vergleich zusammen; die vollständigen Aufzeichnungen befinden sich in evidence/.
| Beobachtung | Angreifbar 2.426.2 | Gepatcht 2.426.3 |
|---|---|---|
@-Token-Verarbeitung | in Dateiinhalte expandiert | als Literalzeichenfolge behandelt |
readuser + connect-node | vollständige Dateioffenlegung (3 von 3 Zeilen) | keine Offenlegung |
anonymous + who-am-i / help | teilweiser Leak (erste Zeile) durch Parser-Fehler vor der Authentifizierungsschranke | keine Offenlegung |
Markierung POC-PROOF-LINE in Ausgabe | vorhanden | abwesend |
Die einzige Variable, die sich zwischen den beiden Spalten ändert, ist die Jenkins-Minor-Version und dadurch der Standardwert von atSyntax nach dem Patch. Die gegensätzlichen Ergebnisse führen die Verhaltensänderung daher auf den Parser-Fix in Commit 554f0378 zurück.
Jenkins in einem Container auszuführen, ist keine Abhilfe. Der Parser liest Dateien mit den Berechtigungen der Jenkins-JVM, und diese Dateien befinden sich im selben Container-Dateisystem, das auch den Credentials-Speicher enthält. Die Containergrenze schützt den Host vor dem Jenkins-Prozess, nicht den Jenkins-Prozess vor sich selbst. Das Docker-Setup hier ist eine Demonstrations-Sandbox, nichts weiter.