O endpoint do plain CLI do Jenkins (/cli?remoting=false) emparelha duas requisições POST (download/upload) por meio de um UUID de Session compartilhado e é acessível sem permissão Overall/Read.
Dois bugs se combinam: Um HashMap sem sincronização em CLIAction perde uma metade da sessão (race condition), e os loops de espera do protocolo CLI (ServerSideImpl.run, FullDuplexHttpService.upload) não têm timeout.
Atacantes podem concluir suas chamadas HTTP em milissegundos enquanto deixam threads do Jetty bloqueadas por 15 segundos (janela da race condition) ou indefinidamente (protocolo abandonado), causando um DoS assimétrico em todo o controller.
Afeta core ≤ 2.540 e LTS ≤ 2.528.2 (CWE-362/CWE-404, CVSS: 7,5). Corrigido em 2.541 e 2.528.3 usando ConcurrentHashMap, adicionando timeouts no handshake e fechando streams em caso de erro.
Mitigue agora: Atualize, ou bloqueie o acesso ao endpoint do plain CLI a partir de redes não confiáveis e capture thread dumps para confirmar que não há threads travadas em CLIAction$ServerSideImpl.run ou FullDuplexHttpService.upload/download.
O que o endpoint faz
O CLI sem Remoting constrói um canal full-duplex a partir de duas requisições POST HTTP:
Lado download:Side: download, abre /cli?remoting=false, o servidor escreve um byte e aguarda a metade upload
Lado upload:Side: upload, mesmo UUID de Session, fornece o input stream
hudson.cli.CLIAction conecta isso ao jenkins.util.FullDuplexHttpService, armazenando as sessões ativas em um registro compartilhado entre requisições.
Causa raiz #1: Registro de sessões sem sincronização (race condition)
CLIAction mantém o mapa de sessões em um HashMap simples compartilhado por todas as threads de requisição:
// core/src/main/java/hudson/cli/CLIAction.java:83privatefinaltransient Map<UUID, FullDuplexHttpService> duplexServices = new HashMap<>();
// core/src/main/java/hudson/cli/CLIAction.java:83privatefinaltransient Map<UUID, FullDuplexHttpService> duplexServices = new HashMap<>();
// core/src/main/java/hudson/cli/CLIAction.java:83privatefinaltransient Map<UUID, FullDuplexHttpService> duplexServices = new HashMap<>();
// core/src/main/java/hudson/cli/CLIAction.java:83privatefinaltransient Map<UUID, FullDuplexHttpService> duplexServices = new HashMap<>();
FullDuplexHttpService.Response.generateResponse chama services.put(uuid, service) para o lado download e services.get(uuid) para o lado upload. Como HashMap não é thread-safe, puts/gets concorrentes sob carga podem
retornar null para um lado download válido;
perder entradas durante um resize;
deixar threads de download dentro de FullDuplexHttpService.download aguardando até 15 segundos por um upload que já chegou.
O resultado: Cada par afetado pela race condition bloqueia uma thread do servlet durante todo o timeout enquanto os sockets do atacante se fecham imediatamente, causando um DoS assimétrico e violando o requisito de segurança “Tornar fluxos de lógica crítica thread safe”.
Causa raiz #2: Timeouts ausentes no protocolo (bloqueio determinístico)
Mesmo quando as duas metades se emparelham corretamente, o handshake do protocolo pode entrar em deadlock porque nenhum lado tem timeout:
// core/src/main/java/hudson/cli/CLIAction.java:300synchronized(this){while(!ready){// waits forever if client never sends "start"wait();}}// core/src/main/java/jenkins/util/FullDuplexHttpService.java:145while(!completed){// no deadline if download side dies earlywait();}
// core/src/main/java/hudson/cli/CLIAction.java:300synchronized(this){while(!ready){// waits forever if client never sends "start"wait();}}// core/src/main/java/jenkins/util/FullDuplexHttpService.java:145while(!completed){// no deadline if download side dies earlywait();}
// core/src/main/java/hudson/cli/CLIAction.java:300synchronized(this){while(!ready){// waits forever if client never sends "start"wait();}}// core/src/main/java/jenkins/util/FullDuplexHttpService.java:145while(!completed){// no deadline if download side dies earlywait();}
// core/src/main/java/hudson/cli/CLIAction.java:300synchronized(this){while(!ready){// waits forever if client never sends "start"wait();}}// core/src/main/java/jenkins/util/FullDuplexHttpService.java:145while(!completed){// no deadline if download side dies earlywait();}
Se o cliente encerrar a conexão antes de enviar frames CLI, a thread de download fica bloqueada em ServerSideImpl.run() e a thread de upload fica bloqueada em upload()sem timeout, consumindo duas threads do Jetty por tentativa até esgotar o controller.
Notas de exploração
Ambos os vetores exigem apenas conectividade de rede ao /cli:
Cenário A (race condition): Dispare pares download/upload sobrepostos com leve jitter para que o lado upload ocasionalmente receba null. As threads se acumulam em FullDuplexHttpService.download por cerca de 15 segundos cada.
Cenário B (abandono): Abra ambas as metades, deixe-as emparelhar, e depois feche sem enviar frames CLI. As threads ficam em CLIAction$ServerSideImpl.run e FullDuplexHttpService.upload indefinidamente, sem necessidade de janela de timing.
Abaixo estão as PoCs exatas compartilhadas com a equipe de segurança do Jenkins:
PoC: racecond_a.py (race no HashMap, dois downloads por UUID)
Thread dump (Cenário B, controller de teste Jenkins 2.516.2): Travados exatamente nos call sites sem timeout:
at hudson.cli.CLIAction$ServerSideImpl.run(CLIAction.java:319)
- locked <0x00000000f0d9ba90> (a hudson.cli.CLIAction$ServerSideImpl)at jenkins.util.FullDuplexHttpService.download(FullDuplexHttpService.java:119)at jenkins.util.FullDuplexHttpService.upload(FullDuplexHttpService.java:146)
- locked <0x00000000f0d9aaa0> (a hudson.cli.CLIAction$PlainCliEndpointResponse$1)at jenkins.util.FullDuplexHttpService$Response.generateResponse(FullDuplexHttpService.java:191)
at hudson.cli.CLIAction$ServerSideImpl.run(CLIAction.java:319)
- locked <0x00000000f0d9ba90> (a hudson.cli.CLIAction$ServerSideImpl)at jenkins.util.FullDuplexHttpService.download(FullDuplexHttpService.java:119)at jenkins.util.FullDuplexHttpService.upload(FullDuplexHttpService.java:146)
- locked <0x00000000f0d9aaa0> (a hudson.cli.CLIAction$PlainCliEndpointResponse$1)at jenkins.util.FullDuplexHttpService$Response.generateResponse(FullDuplexHttpService.java:191)
at hudson.cli.CLIAction$ServerSideImpl.run(CLIAction.java:319)
- locked <0x00000000f0d9ba90> (a hudson.cli.CLIAction$ServerSideImpl)at jenkins.util.FullDuplexHttpService.download(FullDuplexHttpService.java:119)at jenkins.util.FullDuplexHttpService.upload(FullDuplexHttpService.java:146)
- locked <0x00000000f0d9aaa0> (a hudson.cli.CLIAction$PlainCliEndpointResponse$1)at jenkins.util.FullDuplexHttpService$Response.generateResponse(FullDuplexHttpService.java:191)
at hudson.cli.CLIAction$ServerSideImpl.run(CLIAction.java:319)
- locked <0x00000000f0d9ba90> (a hudson.cli.CLIAction$ServerSideImpl)at jenkins.util.FullDuplexHttpService.download(FullDuplexHttpService.java:119)at jenkins.util.FullDuplexHttpService.upload(FullDuplexHttpService.java:146)
- locked <0x00000000f0d9aaa0> (a hudson.cli.CLIAction$PlainCliEndpointResponse$1)at jenkins.util.FullDuplexHttpService$Response.generateResponse(FullDuplexHttpService.java:191)
Evidência em vídeo: Reprodução completa do crash contra um controller recém-instalado.
Em um build vulnerável, você verá dezenas de threads de requisição aguardando nesses call sites, e chamadas CLI regulares começarão a dar timeout.
Impacto
DoS não autenticado: Não é necessário Overall/Read para acessar /cli?remoting=false
Baixo custo para o atacante: Os sockets fecham imediatamente, o servidor retém o trabalho (15 segundos por tentativa de race, infinito para abandono)
Degradação de todo o controller: As threads do servlet e os fluxos de E/S se acumulam, outros endpoints começam a dar timeout
CLIAction agora armazena as sessões em um ConcurrentHashMap, eliminando as perdas do HashMap com race conditions que potencializavam o DoS assimétrico.
ServerSideImpl.run e FullDuplexHttpService.upload adotaram waits limitados por CONNECTION_TIMEOUT com wake-ups de 1 segundo e logs de DEBUG, de modo que handshakes abandonados se desfazem em vez de estacionar threads.
PlainCLIProtocol agora sempre chama side.handleClose() em um bloco finally, garantindo que ambas as metades se desmontem mesmo diante de erros de leitura ou exceções de runtime.
Foi adicionada cobertura de regressão em Security3630Test (JUnit 5): Reduz o timeout do CLI para testes, exercita a race condition anterior com invocações CLI concorrentes e verifica que as threads são liberadas após streams truncados.
Efeito final: O emparelhamento de download/upload do CLI agora falha rápido e libera threads do Jetty em vez de bloquear indefinidamente aguardando contrapartes ausentes.
SECURITY-3630 recebeu o identificador CVE-2025-67635 (CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). Veja o registro do CNA em cve.org e a entrada do NIST no NVD para metadados canônicos.
Se você não puder atualizar imediatamente, faça o seguinte:
Desabilite ou bloqueie o acesso ao endpoint do plain CLI a partir de redes não confiáveis; prefira o WebSocket CLI com autenticação adequada.
Reduza os limites de threads do Jetty somente como último recurso (não elimina o bug).
Monitore thread dumps buscando estados de wait em CLIAction$ServerSideImpl.run e FullDuplexHttpService.upload/download.
Indicadores de comprometimento
Mensagens repetidas de IOException: No download side found for <uuid> nos logs.
Thread dumps mostrando muitos TIMED_WAITING em FullDuplexHttpService.download ou WAITING em CLIAction$ServerSideImpl.run / FullDuplexHttpService.upload.
Picos de requisições a /cli?remoting=false sem headers de autenticação.
Aplique o patch imediatamente. Este é um path de DoS fácil e acessível pela rede em instalações de Jenkins com configuração padrão.
As soluções da Fluid Attacks permitem que as organizações identifiquem, priorizem e corrijam vulnerabilidades em seus softwares ao longo do SDLC. Com o apoio de IA, ferramentas automatizadas e pentesters, a Fluid Attacks acelera a mitigação da exposição ao risco das empresas e fortalece sua postura de cibersegurança.
As soluções da Fluid Attacks permitem que as organizações identifiquem, priorizem e corrijam vulnerabilidades em seus softwares ao longo do SDLC. Com o apoio de IA, ferramentas automatizadas e pentesters, a Fluid Attacks acelera a mitigação da exposição ao risco das empresas e fortalece sua postura de cibersegurança.
As soluções da Fluid Attacks permitem que as organizações identifiquem, priorizem e corrijam vulnerabilidades em seus softwares ao longo do SDLC. Com o apoio de IA, ferramentas automatizadas e pentesters, a Fluid Attacks acelera a mitigação da exposição ao risco das empresas e fortalece sua postura de cibersegurança.