Neutron, nuestro motor de IA, obtuvo un 97.1% en el benchmark CyberGym de UC Berkeley. Más información

Análisis a fondo

Análisis a fondo

Kubeflow Pipelines: ejecución de código entre inquilinos (CVE-2026-77596)

Kubeflow Pipelines anterior a 2.18.0 omite la autorización de pipelines compartidos y permite ejecutar código en otro espacio de nombres. Corregido en 2.18.0.

Resumen

  • Un inquilino con pocos privilegios puede subir un pipeline compartido, o una nueva versión de un pipeline compartido existente, sin una SubjectAccessReview de Kubernetes. El aviso se publicó el 28 de octubre de 2026.
  • Kubeflow Pipelines anterior a 2.18.0 está afectado en modo multiusuario cuando REQUIRE_NAMESPACE_FOR_PIPELINES es false, que es el valor predeterminado. Una ejecución recurrente no fijada que sigue la última versión ejecuta entonces el pipeline inyectado en el espacio de nombres de otro inquilino.
  • El registro CVE lo califica como CVSS v3.1 9.6 Critical. No se ha publicado ninguna prueba de concepto y no se ha observado explotación en entornos reales; la CVE no está en el catálogo KEV de la CISA a 6 de noviembre de 2026.
  • Actualice a Kubeflow Pipelines 2.18.0 o posterior. Si no puede, exija un espacio de nombres en las operaciones de pipeline y coloque un proxy de autenticación delante del servidor de API; consulte "¿Cómo lo corrijo?".

¿Estoy afectado?

Está expuesto solo cuando se cumplen todas estas condiciones:

  • El clúster ejecuta Kubeflow Pipelines anterior a 2.18.0. El aviso enumera las versiones hasta 2.17.3 como afectadas.
  • El servidor de API funciona en modo multiusuario. Las instalaciones de un solo usuario omiten por completo la ruta de autorización y no están afectadas por este fallo.
  • REQUIRE_NAMESPACE_FOR_PIPELINES es false. El valor predeterminado es false y no se define en los manifiestos distribuidos, así que una instalación multiusuario predeterminada está expuesta.
  • El atacante puede alcanzar el servidor de API. Cuando es accesible directamente, sin proxy de autenticación, un llamante no autenticado puede usar el bypass. Detrás de un proxy de autenticación, el llamante necesita una identidad de inquilino válida con pocos privilegios.
  • Para el paso de ejecución de código, el espacio de nombres de destino tiene una ejecución recurrente no fijada que sigue la última versión del pipeline. Una ejecución fijada, o una ejecución que incorpora su especificación, no se sustituye.

El servidor de API es el despliegue ml-pipeline en el espacio de nombres kubeflow. Sus puntos de entrada HTTP de subida son POST /apis/v2beta1/pipelines/upload y POST /apis/v2beta1/pipelines/upload_version, y el mismo servidor atiende las rutas v1beta1 anteriores.

Rama Versiones afectadas Versión corregida
Kubeflow Pipelines todas las versiones anteriores a 2.18.0 (aviso: hasta 2.17.3) 2.18.0 (publicada el 28 de octubre de 2026)

Compruebe la etiqueta de la imagen y el ajuste de espacio de nombres:

kubectl -n kubeflow get deploy ml-pipeline -o jsonpath='{range .spec.template.spec.containers[*]}{.name}{" "}{.image}{"\n"}{end}'
kubectl -n kubeflow get deploy ml-pipeline -o yaml | grep -i REQUIRE_NAMESPACE_FOR_PIPELINES

Una etiqueta de imagen inferior a 2.18.0, sin entrada REQUIRE_NAMESPACE_FOR_PIPELINES o con el valor false, coincide con la configuración predeterminada expuesta. Cuando la variable falta, el servidor usa false.

¿Cómo lo corrijo?

  1. Actualice a Kubeflow Pipelines 2.18.0 o posterior. La versión 2.18.0 es la primera que incluye la corrección de autorización. Aplique los manifiestos correspondientes a su método de instalación.
  2. Si no puede actualizar ahora, defina REQUIRE_NAMESPACE_FOR_PIPELINES=true en el servidor de API. La validación rechaza entonces un espacio de nombres vacío antes de que la subida llegue a la rama sin protección. Es una mitigación, no la corrección. Un llamante aún puede indicar un espacio de nombres que no posee, y la comprobación que sigue es la que repara el parche.
  3. Coloque un proxy de autenticación delante del servidor de API y no lo exponga directamente. El aviso indica que un servidor accesible directamente permite a un llamante no autenticado usar el bypass. Un proxy que autentica cada petición elimina esa ruta.
  4. Audite los pipelines compartidos y sus versiones en busca de imágenes de contenedor, comandos y argumentos que no reconozca, y revise las ejecuciones recurrentes que siguen la última versión.

El aviso del proveedor no indica ningún workaround a nivel de código. Actualizar es la única corrección completa.

¿Cuál es la causa raíz?

La ruta HTTP de subida devuelve éxito desde su asistente de autorización siempre que el espacio de nombres de destino está vacío. Así, una subida de pipeline compartido nunca llega a la SubjectAccessReview. El asistente en backend/src/apiserver/server/pipeline_upload_server.go en 2.17.3:

func (s *PipelineUploadServer) canUploadVersionedPipeline(r *http.Request, pipelineID string, resourceAttributes *authorizationv1.ResourceAttributes) error {
    if !common.IsMultiUserMode() {
        // Skip authorization if not multi-user mode.
        return nil
    }
    if len(pipelineID) > 0 {
        namespace, err := s.resourceManager.FetchNamespaceFromPipelineId(pipelineID)
        if err != nil {
            return util.Wrap(err, "Failed to authorize with the Pipeline ID")
        }
        if len(resourceAttributes.Namespace) == 0 {
            resourceAttributes.Namespace = namespace
        }
    }
    if resourceAttributes.Namespace == "" {
        return nil
    }

    resourceAttributes.Group = common.RbacPipelinesGroup
    // ... IsAuthorized() runs only after this point
}

Un espacio de nombres vacío es la forma en que Kubeflow Pipelines direcciona un pipeline compartido en todo el clúster. También es el valor predeterminado cuando un llamante omite el parámetro de consulta namespace. La validación que podría rechazarlo solo se ejecuta cuando IsNamespaceRequiredForPipelines() es true:

func ValidateNamespaceRequired(namespace string) error {
    if common.IsMultiUserMode() &&
        common.IsNamespaceRequiredForPipelines() &&
        namespace == "" {
        return util.NewInvalidInputError("Namespace is required for pipeline operations")
    }
    return nil
}

IsNamespaceRequiredForPipelines() lee REQUIRE_NAMESPACE_FOR_PIPELINES con un valor predeterminado false. Ningún manifiesto bajo manifests/ lo define.

La ruta gRPC para la misma escritura es canAccessPipeline() en pipeline_server.go. Hace lo contrario: reasigna un espacio de nombres vacío al espacio de nombres del sistema para create, update y delete. Un comentario indica que las escrituras de pipelines compartidos deben seguir autorizándose. El asistente HTTP es por tanto una omisión y no una excepción intencionada.

¿Cómo se explota?

  1. El atacante alcanza el servidor de API. Sin proxy de autenticación no hace falta cuenta. Detrás de un proxy, basta una cuenta de inquilino con pocos privilegios.
  2. El atacante envía un POST multipart a /apis/v2beta1/pipelines/upload sin parámetro namespace, o a /apis/v2beta1/pipelines/upload_version con el identificador de un pipeline compartido existente.
  3. canUploadVersionedPipeline() ve un espacio de nombres vacío y devuelve nil. No se envía ninguna SubjectAccessReview.
  4. La subida crea un nuevo pipeline compartido, o adjunta una nueva versión a un pipeline compartido existente. La versión lleva la imagen de contenedor, el comando y los argumentos del atacante.
  5. En el espacio de nombres de otro inquilino, una ejecución recurrente no fijada que sigue la última versión recoge la especificación inyectada.
  6. La ejecución lanza el contenedor del atacante con la cuenta de servicio de esa ejecución en el espacio de nombres víctima. Los permisos de Kubernetes de esa cuenta se aplican entonces al código del atacante.

La forma de la petición es una subida multipart estándar de KFP. No se publica código de prueba de concepto; el aviso describe los puntos de entrada y la comprobación que falta.

¿Cómo se desarrolla el ataque?

Un espacio de nombres vacío omite la llamada de autorización, y una versión de pipeline compartido lleva el contenedor del atacante a la ejecución recurrente de otro inquilino.

flowchart LR A["Llamante con pocos privilegios o anónimo"] --> B["POST de subida de pipeline sin espacio de nombres"] B --> C["canUploadVersionedPipeline ve un espacio de nombres vacío"] C --> D["No se envía ninguna SubjectAccessReview"] D --> E["Se guarda el pipeline compartido o la nueva versión"] E --> F["La ejecución recurrente no fijada de otro inquilino sigue la última versión"] F --> G["El contenedor del atacante se ejecuta con la cuenta de servicio de la ejecución víctima"]

¿Qué puede hacer un atacante?

Un atacante puede ejecutar un contenedor arbitrario en el espacio de nombres de otro inquilino, con la cuenta de servicio de la ejecución recurrente de ese inquilino. Todas las condiciones se enumeran aquí:

  • Kubeflow Pipelines anterior a 2.18.0.
  • Modo multiusuario, con REQUIRE_NAMESPACE_FOR_PIPELINES en false o ausente.
  • El servidor de API accesible por el atacante, directamente o mediante un proxy de autenticación que admita una cuenta con pocos privilegios.
  • Un pipeline compartido existente para la ruta de inyección de versión, o permiso para crear un nuevo pipeline compartido para la ruta de creación.
  • En el espacio de nombres víctima, una ejecución recurrente no fijada que sigue la última versión.

El impacto incluye leer los secretos del espacio de nombres que la cuenta de servicio de la ejecución puede leer, modificar sus recursos y usar su posición de red.

El impacto exacto depende de los role bindings de la cuenta de servicio. Una ejecución fijada, o una ejecución que incorpora su propia especificación, no se sustituye por esta ruta. El aviso no demuestra una ruta no autenticada cuando un proxy de autenticación protege el servidor.

¿Por qué funciona la corrección?

La corrección asigna un espacio de nombres vacío al espacio de nombres del sistema de KFP. Deja decidir a la SubjectAccessReview existente, así que una escritura de pipeline compartido se autoriza como cualquier otra escritura. El parche en pipeline_upload_server.go:

    if len(pipelineID) > 0 {
        namespace, err := s.resourceManager.FetchNamespaceFromPipelineId(pipelineID)
        if err != nil {
            return util.Wrap(err, "Failed to authorize with the Pipeline ID")
        }
        if len(resourceAttributes.Namespace) == 0 {
            resourceAttributes.Namespace = namespace
        }
    }
-   if resourceAttributes.Namespace == "" {
-       return nil
+   if s.resourceManager.IsEmptyNamespace(resourceAttributes.Namespace) {
+       // Shared-pipeline writes require authorization in the KFP system namespace.
+       resourceAttributes.Namespace = common.GetPodNamespace()
    }

    resourceAttributes.Group = common.RbacPipelinesGroup

El código ahora llama a IsAuthorized() para las escrituras compartidas, con el espacio de nombres en el que se ejecuta el servidor de API.

Un llamante que no puede crear pipelines en ese espacio de nombres es rechazado. La corrección también añade una matriz de pruebas recordingSubjectAccessReviewClient. Cubre subidas compartidas y con espacio de nombres, subidas de versión, casos permitidos y denegados, y el modo de un solo usuario. La rama de espacio de nombres vacío se ejercita así en CI.

La corrección es completa para los dos puntos de entrada HTTP que nombra el aviso. No cambia el diseño de RBAC: un inquilino que ya tiene permiso de escritura de pipelines compartidos lo conserva, y la decisión de autorización es del clúster.

¿Cómo compruebo si estuve comprometido?

El proveedor no publica indicadores de compromiso. Revise el registro del servidor de API y el inventario de pipelines compartidos:

# Pipeline upload requests and authorization failures in the API server log.
kubectl -n kubeflow logs deploy/ml-pipeline --since=720h | grep -Ei 'upload|SubjectAccessReview|Authorization Failure'

# Shared pipelines visible through the API.
kubectl -n kubeflow port-forward deploy/ml-pipeline 8888:8888 &
curl -s http://localhost:8888/apis/v2beta1/pipelines | head -c 2000

En una instalación multiusuario, un pipeline sin espacio de nombres es un pipeline compartido. Revise cada pipeline compartido y sus versiones, y compare la imagen de contenedor, el comando y los argumentos de cada versión con lo que sus equipos esperan. Después, liste las ejecuciones recurrentes que siguen la última versión y revise su historial reciente en busca de imágenes que no desplegó.

Los registros de auditoría de Kubernetes registran las llamadas SubjectAccessReview cuando la política de auditoría las incluye. En el nivel de auditoría Request o superior, el cuerpo de la revisión incluye la identidad del llamante. Busque la petición de subida en los registros de acceso de ml-pipeline o del proxy, si el registro de peticiones está activado. La ausencia de una SubjectAccessReview no se registra por sí misma.

Una subida correcta es silenciosa en la interfaz de KFP más allá del nuevo pipeline o la nueva versión. Trate un pipeline compartido inesperado, o una nueva versión de un pipeline compartido que no publicó, como una pista que investigar.

Cronología

  • 2026-09-09: la corrección se fusiona mediante la pull request #14352.
  • 2026-10-28: se publica Kubeflow Pipelines 2.18.0 y se publica el aviso GHSA-rjr3-43j3-4q8g.
  • 2026-11-05: se publica CVE-2026-77596 con CVSS v3.1 9.6.
  • 2026-11-06: no se ha notificado explotación en entornos reales, y la CVE no está en el catálogo KEV de la CISA.

La corrección se fusionó 49 días antes de la versión 2.18.0, y el registro CVE llegó 8 días después de la versión.
Figura 1: Del commit de corrección a la versión 2.18.0 y al registro CVE. Fuente: commit 62516d8 de Kubeflow Pipelines, versión 2.18.0 y registro CVE, comprobados el 6 de noviembre de 2026

Preguntas frecuentes

¿Está afectada una instalación de Kubeflow Pipelines de un solo usuario?

No. El asistente vuelve inmediatamente cuando el modo multiusuario está desactivado, así que la ruta de autorización y la rama de espacio de nombres vacío nunca se usan. El fallo afecta a las instalaciones multiusuario.

¿Necesita el atacante una cuenta de Kubernetes?

No cuando el servidor de API es accesible directamente sin proxy de autenticación; el aviso dice que un llamante no autenticado puede usar el bypass en ese caso. Detrás de un proxy de autenticación, el atacante necesita una cuenta de inquilino válida con pocos privilegios.

¿El código del atacante se ejecuta siempre en el espacio de nombres de otro inquilino?

No. Se ejecuta cuando el espacio de nombres de destino tiene una ejecución recurrente no fijada que sigue la última versión. Una ejecución que fija una versión, o que incorpora su especificación, no se sustituye por esta ruta.

¿Basta con definir REQUIRE_NAMESPACE_FOR_PIPELINES=true?

Bloquea la ruta de espacio de nombres vacío, que es el bypass que describe esta CVE. Es una mitigación, no la corrección, y no sustituye la comprobación de autorización que añade el parche. Actualice en cuanto pueda.

¿Se explotó en entornos reales?

No se ha notificado explotación en entornos reales a 6 de noviembre de 2026, y la CVE no está en el catálogo KEV de la CISA. El aviso se publicó el 28 de octubre de 2026, así que la ausencia de informes es reciente.

¿Cómo sé qué versión ejecuto?

Compruebe la etiqueta de imagen del despliegue ml-pipeline. Los manifiestos fijan una etiqueta de imagen por versión, y 2.18.0 es la primera versión corregida. Si su proveedor de plataforma incluye Kubeflow Pipelines, pregunte qué versión upstream contiene el paquete.

Referencias

  • Aviso de seguridad de Kubeflow Pipelines GHSA-rjr3-43j3-4q8g, publicado el 28 de octubre de 2026, con las versiones afectadas, el código vulnerable, la corrección y el impacto: https://github.com/kubeflow/pipelines/security/advisories/GHSA-rjr3-43j3-4q8g (comprobado el 6 de noviembre de 2026)
  • Registro CVE-2026-77596, publicado el 5 de noviembre de 2026, CVSS v3.1 9.6 y rango afectado: https://www.cve.org/CVERecord?id=CVE-2026-77596 (comprobado el 6 de noviembre de 2026)
  • Pull request de Kubeflow Pipelines #14352, la corrección: https://github.com/kubeflow/pipelines/pull/14352 (comprobado el 6 de noviembre de 2026)
  • Commit de Kubeflow Pipelines 62516d8c525f146b754cb515cf441dd0290ad841, "fix(backend): authorize shared pipeline uploads (#14352)", 9 de septiembre de 2026: https://github.com/kubeflow/pipelines/commit/62516d8c525f146b754cb515cf441dd0290ad841 (comprobado el 6 de noviembre de 2026)
  • Versión 2.18.0 de Kubeflow Pipelines, 28 de octubre de 2026: https://github.com/kubeflow/pipelines/releases/tag/2.18.0 (comprobado el 6 de noviembre de 2026)
  • Archivo backend/src/apiserver/server/pipeline_upload_server.go de Kubeflow Pipelines en 2.17.3, el asistente vulnerable: https://github.com/kubeflow/pipelines/blob/2.17.3/backend/src/apiserver/server/pipeline_upload_server.go (comprobado el 6 de noviembre de 2026)
  • Archivo backend/src/apiserver/server/pipeline_server.go de Kubeflow Pipelines en 2.17.3, el canAccessPipeline() gRPC que autoriza las escrituras compartidas: https://github.com/kubeflow/pipelines/blob/2.17.3/backend/src/apiserver/server/pipeline_server.go (comprobado el 6 de noviembre de 2026)
  • Archivo backend/src/apiserver/validation/namespace.go de Kubeflow Pipelines en 2.17.3, ValidateNamespaceRequired(): https://github.com/kubeflow/pipelines/blob/2.17.3/backend/src/apiserver/validation/namespace.go (comprobado el 6 de noviembre de 2026)
  • Archivo manifests/kustomize/base/pipeline/ml-pipeline-apiserver-deployment.yaml de Kubeflow Pipelines en 2.18.0, el despliegue ml-pipeline: https://github.com/kubeflow/pipelines/blob/2.18.0/manifests/kustomize/base/pipeline/ml-pipeline-apiserver-deployment.yaml (comprobado el 6 de noviembre de 2026)
  • Fuente JSON del catálogo de vulnerabilidades explotadas de la CISA, comprobada para CVE-2026-77596 el 6 de noviembre de 2026 (no incluida): https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json (comprobado el 6 de noviembre de 2026)
  • GitHub, kubeflow/pipelines, 4,262 estrellas y 2,185 forks: https://github.com/kubeflow/pipelines (comprobado el 6 de noviembre de 2026)