
Reproducción y análisis autocontenidos en Docker de CVE-2024-23897, la lectura arbitraria de archivos de Jenkins CLI mediante la expansión de argumentos con sintaxis @ de args4j.
Una reproducción autocontenida y totalmente local de CVE-2024-23897, la lectura arbitraria de archivos crítica (puntuación base CVSS 3.1 de 9.8) en la CLI de Jenkins. El proyecto levanta dos stacks de Docker que difieren únicamente en la versión menor de Jenkins, ejecuta la misma prueba de concepto contra ambos y muestra cómo la vulnerabilidad se dispara en el controlador sin parchear y se silencia en el parcheado.
Todo está controlado por un único programa en Python, poc.py. El harness solo orquesta el entorno (Docker Compose, el cliente oficial jenkins-cli.jar y la captura verbatim de la salida). La vulnerabilidad en sí reside en el código Java de Jenkins y nunca se reimplementa aquí.
Un análisis escrito completo, incluida la revisión del parche y el desglose de CVSS, está en report/report.pdf.
La CLI de Jenkins construye su analizador de argumentos con la librería args4j. args4j tiene una característica llamada expandAtFiles, controlada por la bandera atSyntax y habilitada por defecto, que reescribe cualquier argumento de la forma en el contenido de ese archivo antes de que se ejecute el comando. El archivo se abre con los privilegios del proceso del controlador de Jenkins. Dado que los tres transportes de la CLI (HTTP, WebSocket, SSH) desembocan en el mismo analizador, cualquier cliente que pueda enviar cualquier comando CLI puede leer archivos arbitrarios del controlador. El parche (commit ) añade una constante cuyo valor por defecto es , desactivando la expansión.
@/ruta/al/archivo554f0378ALLOW_AT_SYNTAXfalseEsto no es un path traversal clásico: no hay ../ ni un directorio base que escapar. La ruta se abre directamente. En el eje del resultado es una lectura arbitraria de archivos; en el eje del mecanismo es una expansión de argumentos.
cve-2024-23897-jenkins-poc/
README.md # este archivo
LICENSE
poc.py # harness de reproducción en Python (todos los subcomandos)
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 # arranca admin + readuser mediante matrix-auth
evidence/
docker-versions.txt # versiones de Docker + Compose del host
output-vulnerable.txt # capturado durante `poc.py exploit`
output-fixed.txt # capturado durante `poc.py verify-fix`
report/
report.pdf # análisis escrito completo
report.tex # fuente LaTeX (autocontenida, sin figuras externas)
| Requisito | Notas |
|---|---|
| Docker Engine 24 o superior | la versión exacta utilizada se registra en evidence/docker-versions.txt |
| Docker Compose v2 | incluido como el plugin integrado docker compose |
| Python 3.8 o superior | solo librería estándar, no se necesita pip install |
| Espacio en disco | alrededor de 1.5 GB por las dos imágenes de Jenkins, la imagen temurin y el plugin matrix-auth |
No se requiere JDK en el host. Java se ejecuta dentro del contenedor atacante. Los archivos compose fijan platform: linux/amd64 para que las imágenes se comporten de forma idéntica en Apple Silicon; esto es una decisión operativa y no afecta a la vulnerabilidad, que es independiente de la plataforma.
Desde dentro del repositorio:
python3 poc.py up-vuln # construye y arranca el stack vulnerable, descarga el jar de la CLI
python3 poc.py place-proof # escribe el archivo marcador inofensivo dentro del controlador
python3 poc.py exploit # ejecuta la PoC, escribe evidence/output-vulnerable.txt
python3 poc.py up-fix # desmonta vuln, construye y arranca el stack parcheado
python3 poc.py verify-fix # ejecuta la misma PoC, escribe evidence/output-fixed.txt
python3 poc.py teardown # detiene y elimina ambos stacks
La primera ejecución tarda unos minutos (descarga de imágenes más instalación del plugin). Las ejecuciones posteriores son mucho más rápidas.
poc.py exploit pasa cuando la cadena marcadora POC-PROOF-LINE aparece en la salida capturada (la filtración ocurrió). poc.py verify-fix pasa con la condición opuesta: el marcador debe estar ausente. Ambos subcomandos salen con código no cero en caso de fallo, por lo que los dos archivos de evidencia junto con su estado de salida son en sí mismos el resultado de la prueba.
Ambos archivos compose ejecutan los mismos dos servicios en una única red Docker: un controlador de Jenkins (la víctima) y un pequeño contenedor atacante eclipse-temurin:17-jre. poc.py se ejecuta en el host y controla Docker mediante subprocess, pero la invocación real de java -jar jenkins-cli.jar ... se ejecuta dentro del contenedor atacante. El atacante no tiene acceso al volumen de datos de Jenkins; solo alcanza al controlador a través de la red, como lo haría un atacante remoto.
init.groovy.d/01-create-users.groovy utiliza el plugin matrix-auth para crear dos cuentas con permisos deliberadamente diferentes:
| Cuenta | Permisos | Rol en la PoC |
|---|---|---|
admin | Jenkins.ADMINISTER | existe solo para cumplir con "al menos un administrador", nunca se usa para atacar |
readuser | solo Jenkins.READ (Overall/Read) | el atacante autenticado |
| anonymous | ninguno | el atacante no autenticado |
Esto reproduce la división exacta del aviso oficial, Overall/Read frente a anonymous, en lugar del más laxo "cualquier usuario conectado frente a anonymous" que habría producido el valor predeterminado del núcleo de Jenkins. Por lo tanto, la filtración completa del archivo es atribuible únicamente al CVE, no al alcance administrativo.
La PoC ejecuta cuatro contextos contra cada controlador. La tabla siguiente resume la comparación A/B; las capturas completas están en evidence/.
| Observación | Vulnerable 2.426.2 | Parcheado 2.426.3 |
|---|---|---|
manejo del token @ | expandido en el contenido del archivo | tratado como cadena literal |
readuser + connect-node | divulgación completa del archivo (3 de 3 líneas) | sin divulgación |
anonymous + who-am-i / help | fuga parcial (primera línea) mediante error del analizador antes de la puerta de autenticación | sin divulgación |
marcador POC-PROOF-LINE en la salida | presente | ausente |
La única variable que cambia entre las dos columnas es la versión menor de Jenkins y, a través de ella, el valor predeterminado posterior al parche de atSyntax. Por lo tanto, los resultados opuestos atribuyen el cambio de comportamiento al parche del analizador en el commit 554f0378.
Ejecutar Jenkins en un contenedor no es una mitigación. El analizador lee archivos con los privilegios de la JVM de Jenkins, y esos archivos viven dentro del mismo sistema de archivos del contenedor que alberga el almacén de credenciales. El límite del contenedor protege al host del proceso de Jenkins, no al proceso de Jenkins de sí mismo. La configuración Docker aquí es un sandbox de demostración, nada más.
Actualice a una versión parcheada: 2.442 (semanal), o 2.426.3 o 2.440.1 (LTS), todas publicadas el 24 de enero de 2024. Si una actualización inmediata no es posible, deje la propiedad de sistema hudson.cli.CLICommand.allowAtSyntax sin establecer (su valor predeterminado), lo que mantiene ALLOW_AT_SYNTAX en false y desactiva la expansión. Deshabilitar transportes individuales de la CLI es solo una medida parcial, ya que los tres convergen en el mismo analizador.
Todo el trabajo se ejecuta en un entorno Docker local contra un archivo marcador inofensivo de tres líneas (/tmp/poc-proof.txt) que el propio harness crea. No se escanea ni se contacta ninguna instancia pública de Jenkins, y nunca se lee ningún secreto real como secrets/master.key o credentials.xml.
554f0378 en jenkinsci/jenkins: https://github.com/jenkinsci/jenkins/commit/554f03782057c499c49bbb06575f0d28b5200edbParserProperties.withAtSyntax): https://github.com/kohsuke/args4j