sistema: OPERATIVO
← volver a todos los hacks
SUPPLY CHAIN CRITICAL NEW

ktransformers: RCE no autenticada vía pickle sobre ZeroMQ (CVE-2026-26210)

Una RCE crítica en el motor de inferencia ktransformers expone un socket ZMQ en todas las interfaces y deserializa con pickle todo lo que recibe. Es el caso más reciente del patrón «ShadowMQ» copiado entre los stacks de inferencia de IA.

2026-06-15 // 6 min affects: ktransformers, vllm, sglang, nvidia-tensorrt-llm, meta-llama-stack

¿Qué es esto?

CVE-2026-26210 es un fallo crítico de ejecución remota de código (RCE) sin autenticación en ktransformers, un motor de inferencia de LLM de alto rendimiento mantenido por KVCache.AI (unas 16 500 estrellas en GitHub, conocido por ejecutar DeepSeek-V3 en un solo nodo). Fue divulgado por Valentin Lobstein (Chocapikk) como parte de una auditoría de la deserialización pickle en frameworks de inferencia de ML, asignado por VulnCheck y publicado el 23 de abril de 2026. El NVD lo califica con CVSS 3.1 9.8 (y CVSS 4.0 9.3), lo clasifica como CWE-502 (deserialización de datos no confiables) y afecta a ktransformers hasta la 0.5.3.

La causa raíz no es nueva, y ese es justamente el punto. Se trata de la misma combinación insegura de ZeroMQ y el módulo pickle de Python que Oligo Security documentó en noviembre de 2025 bajo el nombre de ShadowMQ, un patrón que se propagó por todo el ecosistema de IA (Meta Llama Stack, vLLM, NVIDIA TensorRT-LLM, Modular Max, SGLang) en gran medida por copia de código de serving.

Cómo funciona

El backend balance_serve de ktransformers (su modo multiconcurrencia heredado, activado con --backend_type balance_serve) inicia un planificador que vincula un socket ZeroMQ ROUTER a todas las interfaces de red y luego reenvía los mensajes entrantes a hilos de trabajo. Cada worker deserializa los bytes en bruto que recibe:

# worker del planificador, simplificado
self.frontend.bind(f"tcp://*:{sched_port}")   # escucha en todas las interfaces
# ... proxy ROUTER/DEALER hacia los hilos de trabajo ...
message = worker.recv()
data = pickle.loads(message)                   # CWE-502: entrada no confiable

pickle.loads() no es un parser seguro: deserializar un objeto controlado por el atacante puede provocar la ejecución de código arbitrario durante el unpickling. Como el socket no exige ninguna autenticación ni validación, cualquier host que alcance el puerto puede ejecutar código con los privilegios del proceso ktransformers. Omitimos deliberadamente cualquier payload funcional; el mecanismo (un pickle malicioso que se reduce a un comando del sistema) es CWE-502 clásico y solo se describe para explicar el riesgo.

Dos detalles hacen realista la explotación en producción. Primero, el despliegue Docker oficial se ejecuta con --network=host, lo que elimina el aislamiento de red del contenedor y expone el puerto ZMQ en el host. Segundo, aunque el puerto se asigna dinámicamente, queda registrado en los logs del servidor y los sockets ZMQ se identifican trivialmente en la red: Oligo detectó miles de sockets ZMQ expuestos a Internet público. La ficha SSVC de CISA señala una madurez de explotación «proof-of-concept» y un carácter automatizable.

Por qué importa

Los servidores de inferencia se sitúan en lo más profundo de la infraestructura de IA: contienen los pesos de los modelos, los prompts, las claves de API y a menudo se ejecutan en clústeres de GPU con amplio alcance a la red interna. Una RCE en un nodo puede significar ejecución de código en todo el clúster, movimiento lateral, exfiltración de secretos y modelos, o criptominería en GPU: la misma clase de impacto que los casos anteriores de ShadowMQ.

La lección más amplia trata de la cadena de suministro por imitación. El fallo no se propagó mediante una dependencia compartida, sino mediante modismos de código compartidos: patrones de recv_pyobj() y «pickle sobre ZMQ» copiados entre proyectos que avanzan deprisa, a veces con el comentario de cabecera todavía indicando «Adapted from vLLM». ktransformers es simplemente el stack más reciente en heredar el patrón, lo que significa que el siguiente probablemente ya esté en producción.

Defensas

La corrección se conoce bien y el parche aguas arriba (PR #1944) la sigue:

  • Actualizar más allá de 0.5.3, o evitar por completo el backend balance_serve. La vía de despliegue moderna integrada con SGLang no inicia el socket ZMQ vulnerable.
  • Nunca hacer pickle.loads() con datos no confiables. Use un formato no ejecutable (JSON o MessagePack) para el RPC; las peticiones de inferencia son datos estructurados que no necesitan pickle. Así es exactamente como Meta, vLLM y Modular corrigieron sus variantes de ShadowMQ.
  • Vincular el socket a 127.0.0.1 por defecto, no a tcp://*. Exigir una habilitación explícita antes de que un socket escuche en una interfaz enrutable.
  • Autenticar el canal. ZeroMQ admite CurveZMQ (y esquemas HMAC); incluso un secreto compartido frena la explotación oportunista.
  • No ejecutar los contenedores de inferencia con --network=host. Mantenga el aislamiento de red del contenedor y exponga solo la API HTTP prevista.
  • Cazar la exposición: escanee sus propios rangos en busca de los bytes de saludo de ZMQ y confirme que ningún puerto de planificador/RPC sea alcanzable desde fuera del perímetro de confianza.

Estado

ElementoDetalle
CVECVE-2026-26210 (CWE-502)
Afectadoktransformers ≤ 0.5.3 (backend balance_serve)
SeveridadCVSS 3.1 9.8 / CVSS 4.0 9.3 (Crítica)
DivulgaciónAuditoría de código el 11 de febrero de 2026; CVE publicada el 23 de abril de 2026
CorrecciónPR aguas arriba #1944; preferir el despliegue integrado con SGLang
CréditoValentin Lobstein (Chocapikk), asignado por VulnCheck
Patrón«ShadowMQ» — reutilización de ZMQ + pickle entre stacks de serving de IA (Oligo, nov. 2025)

Sources