
Riproduzione e analisi Docker autonome di CVE-2024-23897, la lettura arbitraria di file della CLI di Jenkins tramite l'espansione degli argomenti con sintassi @ di args4j.
Una riproduzione autonoma e completamente locale di CVE-2024-23897, la lettura arbitraria di file critica (punteggio base CVSS 3.1: 9.8) nella CLI di Jenkins. Il progetto avvia due stack Docker che differiscono solo per la versione minore di Jenkins, esegue lo stesso proof of concept su entrambi e mostra la vulnerabilità che si attiva sul controller non aggiornato e che rimane silente su quello aggiornato.
Tutto è gestito da un singolo programma Python, poc.py. L'harness si limita
a orchestrare l'ambiente (Docker Compose, il client ufficiale jenkins-cli.jar
e la cattura integrale dell'output). La vulnerabilità stessa risiede nel
codice Java di Jenkins e non viene mai reimplementata qui.
Un'analisi scritta completa, inclusa la revisione della patch e la scomposizione del CVSS,
si trova in report/report.pdf.
La CLI di Jenkins costruisce il suo parser degli argomenti con la libreria args4j. args4j ha
una funzionalità chiamata expandAtFiles, controllata dal flag atSyntax e abilitata per
impostazione predefinita, che riscrive qualsiasi argomento della forma @/path/to/file nel
contenuto di quel file prima che il comando venga eseguito. Il file viene aperto con i
privilegi del processo del controller Jenkins. Poiché tutti e tre i trasporti CLI
(HTTP, WebSocket, SSH) confluiscono nello stesso parser, qualsiasi client che possa inviare
un qualsiasi comando CLI può leggere file arbitrari dal controller. La correzione (commit
554f0378) aggiunge una costante ALLOW_AT_SYNTAX che per impostazione predefinita è false,
disattivando l'espansione.
Non si tratta di path traversal classico: non c'è alcun ../ né una directory di base da
cui uscire. Il percorso viene aperto direttamente. Sull'asse dell'esito è una lettura arbitraria
di file; sull'asse del meccanismo è espansione di argomenti.
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)
| Requisito | Note |
|---|
Nessun JDK è richiesto sull'host. Java viene eseguito all'interno del container dell'attaccante. I
file compose impostano platform: linux/amd64 così le immagini si comportano in modo identico su
Apple Silicon; questa è una scelta operativa e non tocca la
vulnerabilità, che è indipendente dalla piattaforma.
Dall'interno del repository:
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 prima esecuzione richiede alcuni minuti (pull delle immagini più installazione del plugin). Le esecuzioni successive sono molto più veloci.
poc.py exploit ha esito positivo quando la stringa marcatore POC-PROOF-LINE appare
nell'output catturato (la fuga è avvenuta). poc.py verify-fix ha esito positivo nella condizione opposta:
il marcatore deve essere assente. Entrambi i sottocomandi escono con codice non zero in caso di fallimento,
quindi i due file di evidenza insieme al loro stato di uscita costituiscono essi stessi il risultato del test.
Entrambi i file compose eseguono gli stessi due servizi su una rete Docker: un Jenkins
controller (la vittima) e un piccolo container attaccante eclipse-temurin:17-jre.
poc.py viene eseguito sull'host e pilota Docker tramite subprocess, ma l'invocazione
vera e propria java -jar jenkins-cli.jar ... viene eseguita all'interno del container attaccante.
L'attaccante non ha accesso al volume dati di Jenkins; raggiunge il controller
solo tramite la rete, come farebbe un attaccante remoto.
init.groovy.d/01-create-users.groovy usa il plugin matrix-auth per creare due
account con permessi volutamente diversi:
Questo riproduce esattamente la suddivisione dell'avviso ufficiale, Overall/Read contro anonymous, invece della più generica "qualsiasi utente autenticato contro anonymous" che l'impostazione predefinita del core di Jenkins avrebbe prodotto. La fuga completa del file è quindi attribuibile alla sola CVE, non alle prerogative amministrative.
Il PoC esegue quattro contesti contro ciascun controller. La tabella seguente riassume
il confronto A/B; le catture complete sono in evidence/.
L'unica variabile che cambia tra le due colonne è la versione minore di Jenkins
e, attraverso essa, l'impostazione predefinita di atSyntax successiva alla patch. I risultati
opposti attribuiscono quindi il cambiamento di comportamento alla correzione del parser nel commit
554f0378.
Eseguire Jenkins in un container non è una mitigazione. Il parser legge i file con i privilegi della JVM di Jenkins e questi file risiedono nello stesso filesystem del container che contiene l'archivio delle credenziali. Il confine del container protegge l'host dal processo Jenkins, non il processo Jenkins da se stesso. La configurazione Docker qui presente è una sandbox dimostrativa, nient'altro.
Aggiornare a una release con patch: 2.442 (weekly), oppure 2.426.3 o 2.440.1 (LTS), tutte
pubblicate il 24 gennaio 2024. Se un aggiornamento immediato non è possibile, lasciare la
proprietà di sistema hudson.cli.CLICommand.allowAtSyntax non impostata (il suo valore predefinito), che
mantiene ALLOW_AT_SYNTAX su false e disabilita l'espansione. Disabilitare i singoli
trasporti CLI è solo una misura parziale, poiché tutti e tre convergono sullo stesso
parser.
Tutto il lavoro si svolge in un ambiente Docker locale contro un innocuo file marcatore di tre righe
(/tmp/poc-proof.txt) che l'harness crea da sé. Nessuna istanza Jenkins pubblica
viene scansionata o contattata, e nessun segreto reale come
secrets/master.key o credentials.xml viene mai letto.
554f0378 su jenkinsci/jenkins: https://github.com/jenkinsci/jenkins/commit/554f03782057c499c49bbb06575f0d28b5200edbParserProperties.withAtSyntax): https://github.com/kohsuke/args4j| Docker Engine 24 o successivo | la versione esatta utilizzata è registrata in evidence/docker-versions.txt |
| Docker Compose v2 | fornito come plugin integrato docker compose |
| Python 3.8 o successivo | solo libreria standard, nessuna necessità di pip install |
| Spazio su disco | circa 1,5 GB per due immagini Jenkins, l'immagine temurin e il plugin matrix-auth |
| Account | Permessi | Ruolo nel PoC |
|---|
admin | Jenkins.ADMINISTER | esiste solo per soddisfare "almeno un admin", mai usato per attaccare |
readuser | solo Jenkins.READ (Overall/Read) | l'attaccante autenticato |
| anonymous | nessuno | l'attaccante non autenticato |
| Osservazione | Vulnerabile 2.426.2 | Aggiornato 2.426.3 |
|---|
gestione del token @ | espanso nel contenuto del file | trattato come stringa letterale |
readuser + connect-node | divulgazione completa del file (3 righe su 3) | nessuna divulgazione |
anonymous + who-am-i / help | fuga parziale (prima riga) tramite errore del parser prima del gate di autenticazione | nessuna divulgazione |
marcatore POC-PROOF-LINE nell'output | presente | assente |