El endpoint del plain CLI de Jenkins (/cli?remoting=false) empareja dos peticiones POST (download/upload) a través de un UUID de Session compartido y es accesible sin permisos Overall/Read.
Se encadenan dos bugs: Un HashMap no sincronizado en CLIAction pierde una mitad de la sesión (race condition), y los loops de espera del protocolo CLI (ServerSideImpl.run, FullDuplexHttpService.upload) no tienen timeout.
Un atacante puede finalizar sus llamadas HTTP en milisegundos mientras dejan threads de Jetty durante 15 segundos (ventana de la race condition) o indefinidamente (protocolo abandonado), causando DoS asimétrico en todo el controller.
Afecta a core ≤ 2.540 y LTS ≤ 2.528.2 (CWE-362/CWE-404, CVSS: 7,5). Se solucionó en 2.541 y 2.528.3 usando ConcurrentHashMap, agregando timeouts en el handshake y cerrando streams en caso de error.
¿Cómo mitigarlo ahora mismo? Actualiza, o bloquea el acceso al endpoint del plain CLI desde redes no confiables y captura thread dumps para confirmar que no haya threads atascados en CLIAction$ServerSideImpl.run o FullDuplexHttpService.upload/download.
¿Qué hace el endpoint?
El CLI sin Remoting construye un canal de full-duplex a partir de dos POSTs de HTTP:
Lado download:Side: download, abre /cli?remoting=false, el servidor escribe un byte y espera la mitad de upload
Lado de carga:Side: upload, mismo UUID de Session, provee el input stream
hudson.cli.CLIAction conecta esto con jenkins.util.FullDuplexHttpService, almacenando las sesiones activas en un registro común entre peticiones.
Causa raíz #1: Registro de sesiones sin sincronizar (race condition)
CLIAction mantiene el mapa de sesiones en un HashMap simple compartido por todos los threads de petición:
// 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 llama a services.put(uuid, service) para el lado download y a services.get(uuid) para el lado upload. Como HashMap no es thread-safe, puts/gets concurrentes bajo carga pueden
devolver null para un lado download válido;
perder entradas durante un resize;
dejar threads de download dentro de FullDuplexHttpService.download esperando hasta 15 segundos por un upload que ya llegó.
El resultado: Cada par afectado por la race condition bloquea un thread del servlet durante todo el timeout mientras los sockets del atacante se cierran de inmediato, causando un DoS asimétrico y violando el requisito de seguridad “Hacer que los flujos lógicos críticos sean seguros”.
Causa raíz #2: Faltan timeouts en el protocolo (bloqueo determinístico)
Incluso cuando las dos mitades se emparejan correctamente, el handshake del protocolo puede entrar en deadlock porque ningún lado tiene 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();}
Si el cliente corta la conexión antes de enviar frames del CLI, el thread de download se bloquea en ServerSideImpl.run() y el de upload en upload()sin timeout, consumiendo dos threads de Jetty por intento hasta agotar el controller.
Notas de explotación
Ambos vectores solo requieren conectividad de red a /cli:
Escenario A (race condition): Dispara pares download/upload superpuestos con ligero jitter para que el lado upload ocasionalmente vea null. Los threads se acumulan en FullDuplexHttpService.download por ~15 segundos cada uno.
Escenario B (abandono): Abre ambas mitades, déjalas que se emparejen, luego cierra sin enviar frames del CLI. Los threads quedan en CLIAction$ServerSideImpl.run y en FullDuplexHttpService.upload indefinidamente, sin necesidad de ventana de timing.
A continuación se presentan las mismas PoCs compartidas con el equipo de seguridad de Jenkins:
PoC: racecond_a.py (race en HashMap, dos downloads por UUID)
Thread dump (Escenario B, controller de prueba Jenkins 2.516.2): Atascado exactamente en los call sites sin 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)
Evidencia en video: Reproducción del crashend-to-end contra un controller recién instalado:
En un build vulnerable, verás docenas de threads de solicitud esperando en esos call sites, y las llamadas CLI regulares comenzarán a dar timeout.
Impacto
DoS no autenticado: No se requiere Overall/Read para acceder a /cli?remoting=false
Bajo costo para el atacante: Los sockets se cierran de inmediato, el servidor retiene el trabajo (15 segundos por intento de race, infinitamente por abandono)
Degradación del controller completo: Los threads del servlet y los stream de E/S se acumulan, otros endpoints empiezan a dar timeout
CLIAction ahora almacena las sesiones en un ConcurrentHashMap, eliminando las pérdidas del HashMap que potenciaban el DoS asimétrico.
ServerSideImpl.run y FullDuplexHttpService.upload adoptaron waits acotados por CONNECTION_TIMEOUT con wake-ups de 1 segundo y logs de DEBUG, de modo que los handshakes abandonados se deshacen en lugar de estacionar threads.
PlainCLIProtocol ahora siempre llama a side.handleClose() en un bloque finally, asegurando que ambas mitades se desmonten incluso ante errores de lectura o excepciones de runtime.
Se agregó cobertura de regresión en Security3630Test (JUnit 5): Reduce el timeout del CLI para test, ejercita la race condition con invocaciones CLI concurrentes y verifica que los threads se liberen después de streams truncados.
Efecto neto: El emparejamiento download/upload ahora falla rápido y libera threads de Jetty en vez de bloquearse indefinidamente esperando contrapartes faltantes.
A SECURITY-3630 se le asignó CVE-2025-67635 (CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). Consulta el registro del CNA en cve.org y la entrada de NIST en NVD para metadatos canónicos.
Si no puedes actualizar inmediatamente, haz lo siguiente:
Deshabilita o aplica firewall al endpoint de plain CLI; utiliza preferentemente el WebSocket CLI con autenticación adecuada.
Reduce los límites de threads de Jetty solo como último recurso (no elimina el bug).
Monitorea thread dumps buscando estados de wait en CLIAction$ServerSideImpl.run y FullDuplexHttpService.upload/download.
Indicadores de compromiso
Mensajes repetidos de IOException: No download side found for <uuid> en los logs.
Thread dumps que muestran muchos TIMED_WAITING en FullDuplexHttpService.download o WAITING en CLIAction$ServerSideImpl.run / FullDuplexHttpService.upload.
Picos en peticiones a /cli?remoting=false sin encabezados de autenticación.
Actualiza de inmediato. Este es un path de DoS servido en bandeja, alcanzado por red en deployments de Jenkins con configuración por defecto.
Las soluciones de Fluid Attacks permiten a las organizaciones identificar, priorizar y remediar vulnerabilidades en su software a lo largo del SDLC. Con el apoyo de la IA, herramientas automatizadas y pentesters, Fluid Attacks acelera la mitigación de la exposición al riesgo de las empresas y fortalece su postura de ciberseguridad.
Las soluciones de Fluid Attacks permiten a las organizaciones identificar, priorizar y remediar vulnerabilidades en su software a lo largo del SDLC. Con el apoyo de la IA, herramientas automatizadas y pentesters, Fluid Attacks acelera la mitigación de la exposición al riesgo de las empresas y fortalece su postura de ciberseguridad.
Las soluciones de Fluid Attacks permiten a las organizaciones identificar, priorizar y remediar vulnerabilidades en su software a lo largo del SDLC. Con el apoyo de la IA, herramientas automatizadas y pentesters, Fluid Attacks acelera la mitigación de la exposición al riesgo de las empresas y fortalece su postura de ciberseguridad.