
Reprodução e análise autocontida em Docker de CVE-2024-23897, a leitura arbitrária de arquivos via Jenkins CLI através da expansão de argumentos @-syntax do args4j.
Uma reprodução autocontida e totalmente local de CVE-2024-23897, a leitura arbitrária de arquivo crítica (pontuação base CVSS 3.1 9.8) no Jenkins CLI. O projeto cria duas pilhas Docker que diferem apenas pela versão menor do Jenkins, executa a mesma prova de conceito contra ambas e mostra a vulnerabilidade disparando no controlador não corrigido e silenciando no corrigido.
Tudo é controlado por um único programa Python, poc.py. A estrutura apenas orquestra o ambiente (Docker Compose, o cliente oficial jenkins-cli.jar e a captura literal da saída). A vulnerabilidade em si reside no próprio código Java do Jenkins e nunca é reimplementada aqui.
Uma análise completa por escrito, incluindo a revisão do patch e a decomposição do CVSS, está em report/report.pdf.
O Jenkins CLI constrói seu analisador de argumentos com a biblioteca args4j. args4j tem um recurso chamado expandAtFiles, controlado pela flag atSyntax e habilitado por padrão, que reescreve qualquer argumento na forma @/caminho/para/arquivo no conteúdo desse arquivo antes que o comando seja executado. O arquivo é aberto com os privilégios do processo controlador do Jenkins. Como todos os três transportes CLI (HTTP, WebSocket, SSH) convergem para o mesmo analisador, qualquer cliente que possa enviar qualquer comando CLI pode ler arquivos arbitrários do controlador. A correção (commit 554f0378) adiciona uma constante ALLOW_AT_SYNTAX que padrão é false, desativando a expansão. Isso não é path traversal clássico: não há ../ e nenhum diretório base para escapar. O caminho é aberto diretamente. No eixo de resultado, é uma leitura arbitrária de arquivo; no eixo de mecanismo, é expansão de argumento.
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 |
|---|
Nenhum JDK é necessário no host. O Java é executado dentro do contêiner atacante. Os arquivos compose fixam platform: linux/amd64 para que as imagens se comportem de forma idêntica no Apple Silicon; esta é uma escolha operacional e não afeta a vulnerabilidade, que é independente de plataforma.
Dentro do repositório:
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
A primeira execução leva alguns minutos (puxar imagens mais a instalação do plugin). Execuções subsequentes são muito mais rápidas.
poc.py exploit passa quando a string marcadora POC-PROOF-LINE aparece na saída capturada (o vazamento ocorreu). poc.py verify-fix passa na condição oposta: o marcador deve estar ausente. Ambos os subcomandos saem com código não zero em caso de falha, então os dois arquivos de evidência mais seu status de saída são o próprio resultado do teste.
Ambos os arquivos compose executam os mesmos dois serviços em uma rede Docker: um controlador Jenkins (a vítima) e um pequeno contêiner atacante eclipse-temurin:17-jre. poc.py é executado no host e controla o Docker através de subprocess, mas a invocação real java -jar jenkins-cli.jar ... é executada dentro do contêiner atacante. O atacante não tem acesso ao volume de dados do Jenkins; ele alcança o controlador apenas pela rede, da mesma forma que um atacante remoto faria.
init.groovy.d/01-create-users.groovy usa o plugin matrix-auth para criar duas contas com permissões deliberadamente diferentes:
| Conta |
|---|
Isso reproduz a divisão exata no aviso oficial, Overall/Read versus anônimo, em vez da mais ampla "qualquer usuário logado versus anônimo" que o padrão do núcleo do Jenkins teria produzido. O vazamento completo do arquivo é, portanto, atribuível apenas ao CVE, não ao alcance administrativo.
A PoC executa quatro contextos contra cada controlador. A tabela abaixo resume a comparação A/B; as capturas completas estão em evidence/.
A única variável que muda entre as duas colunas é a versão menor do Jenkins e, por meio dela, o padrão pós-patch de atSyntax. Os resultados opostos, portanto, atribuem a mudança de comportamento à correção do analisador no commit 554f0378.
Executar Jenkins em um contêiner não é uma mitigação. O analisador lê arquivos com os privilégios da JVM do Jenkins, e esses arquivos residem dentro do mesmo sistema de arquivos do contêiner que contém o armazenamento de credenciais. O limite do contêiner protege o host do processo Jenkins, não o processo Jenkins de si mesmo. A configuração Docker aqui é um sandbox de demonstração, nada mais.
Atualize para uma versão corrigida: 2.442 (semanal) ou 2.426.3 ou 2.440.1 (LTS), todas publicadas em 24 de janeiro de 2024. Se uma atualização imediata não for possível, deixe a propriedade do sistema hudson.cli.CLICommand.allowAtSyntax não definida (seu padrão), o que mantém ALLOW_AT_SYNTAX como falso e desativa a expansão. Desabilitar transportes CLI individuais é apenas uma medida parcial, já que todos os três convergem para o mesmo analisador.
Todo o trabalho é executado em um ambiente Docker local contra um arquivo marcador inofensivo de três linhas (/tmp/poc-proof.txt) que a estrutura cria por si mesma. Nenhuma instância pública do Jenkins é escaneada ou contatada, e nenhum segredo real como secrets/master.key ou credentials.xml é jamais lido.
554f0378 no jenkinsci/jenkins: https://github.com/jenkinsci/jenkins/commit/554f03782057c499c49bbb06575f0d28b5200edbParserProperties.withAtSyntax): https://github.com/kohsuke/args4j| Notas |
|---|
| Docker Engine 24 ou mais recente | a versão exata usada está registrada em evidence/docker-versions.txt |
| Docker Compose v2 | distribuído como plugin interno docker compose |
| Python 3.8 ou mais recente | apenas biblioteca padrão, nenhum pip install necessário |
| Espaço em disco | cerca de 1,5 GB para duas imagens Jenkins, a imagem temurin e o plugin matrix-auth |
| Permissões |
|---|
| Papel na PoC |
|---|
admin | Jenkins.ADMINISTER | existe apenas para satisfazer "pelo menos um admin", nunca usado para atacar |
readuser | Jenkins.READ apenas (Overall/Read) | o atacante autenticado |
| anônimo | nenhuma | o atacante não autenticado |
| Observação | Vulnerável 2.426.2 | Corrigido 2.426.3 |
|---|
Manipulação do token @ | expandido para o conteúdo do arquivo | tratado como string literal |
readuser + connect-node | divulgação completa do arquivo (3 de 3 linhas) | nenhuma divulgação |
anônimo + who-am-i / help | vazamento parcial (primeira linha) via erro do analisador antes da porta de autenticação | nenhuma divulgação |
marcador POC-PROOF-LINE na saída | presente | ausente |