lunes, 2 de noviembre de 2026
Vulnerabilidades

GitLab: RCE CVSS 10 en KEV de CISA tras el ataque a Alpine

CISA añade a KEV un RCE con CVSS 10 en GitLab escondido en el motor de regex de Ruby; el caso de Alpine muestra que actualizar GitLab quizá no basta.

Ilustración de un contador mecánico desbordándose y haciéndose añicos, que simboliza un desbordamiento de enteros en un motor de expresiones regulares
Un entero que se desborda más allá de su límite es la piedra que inicia la avalancha; aquí, dentro del motor de expresiones regulares de Ruby.

CISA ha añadido CVE-2026-97229 a su catálogo de Vulnerabilidades Explotadas Conocidas (KEV): un desbordamiento de enteros con CVSS 10.0 en GitLab Community Edition y Enterprise Edition que permite a un atacante no autenticado ejecutar código arbitrario en el servidor con nada más exótico que una expresión regular especialmente diseñada en una configuración de CI/CD. Las agencias federales tienen hasta el 1 de noviembre para remediarlo. Y antes de que nadie lo archive como «teórico hasta que se demuestre lo contrario», no lo es. Alguien pasó buena parte de una semana de octubre intentando explotar este fallo exacto contra el propio servidor GitLab del proyecto Alpine Linux, estuvo terriblemente cerca y obligó a Alpine a destruir la instancia y reconstruirla desde cero.

El ataque contra gitlab.alpinelinux.org es el análisis posterior más instructivo que tenemos sobre este fallo, y Alpine merece reconocimiento por publicarlo dejando las incógnitas a la vista. En resumen: el exploit es lo bastante real como para ajustarlo a una versión concreta del objetivo, actualizar GitLab no es necesariamente una solución, y el alcance de un GitLab comprometido es toda tu cadena de suministro de software. Veamos los tres puntos.

El fallo: una expresión regular de distancia a la ejecución de código

La primitiva es un clásico con envoltorio moderno. GitLab compila las expresiones regulares de las configuraciones de CI con Ruby, y el motor de expresiones regulares de Ruby —Onigmo, una biblioteca en C integrada en el intérprete— contiene un desbordamiento o envolvimiento de enteros. Basta con alimentarlo con un patrón manipulado para que la aritmética usada durante la compilación supere un límite, corrompiendo la memoria en un proceso que procesa alegremente entrada controlada por el atacante. La entrada de CISA en KEV lo describe sin rodeos: validación insuficiente de expresiones regulares, y ejecución de código arbitrario no autenticada en el servidor GitLab. Es la misma especie de fallo que diseccionamos en el RCE en el decodificador Brotli: un entero desbordado en una biblioteca C de procesamiento de datos, y el proceso entero queda en manos del atacante.

La cadena es vergonzosamente corta, y eso es lo que hace que el CVSS 10.0 parezca merecido y no inflado. Regístrate en una instancia de GitLab que permita registros. Sube un repositorio con un .gitlab-ci.yml malicioso que incluya la expresión regular manipulada. Los workers de GitLab compilan esa configuración para validarla y ejecutar el pipeline, Onigmo se atraganta, la memoria se corrompe y el atacante consigue ejecución de código en el contexto del worker, en el propio servidor. Sin credenciales, sin sesión, sin ingeniería social: el analizador de CI es la superficie de ataque. Si tu instancia permite que desconocidos se registren y lancen pipelines, tu exposición previa a la autenticación es el formulario de registro.

La primera comprobación de exposición no cuesta nada y responde a la pregunta que realmente importa: qué Ruby es el que está compilando:

$ ruby -v
ruby 3.3.12        # vulnerable — you need 3.3.13, 3.4.12, 4.0.9 or later
# On Alpine hosts, the distro package already carries the fix:
$ apk upgrade        # 3.21 ships Ruby 3.3.13; 3.22/3.23/3.24/edge ship 3.4.12

Ocho caídas, cuatro casi-aciertos y un servidor reconstruido

Según el informe del incidente de Alpine Linux, GitLab publicó la corrección el 12 de octubre y el CVE se hizo público el 13 de octubre; ese día Alpine pretendía actualizar pero instaló la 19.3.4 en lugar de la 19.3.5 parcheada, un error que reconoce en su cronología. Una semana después, el 20 de octubre, una cuenta recién registrada empezó a sondear el servidor, aún vulnerable. Entre el 20 y el 21 de octubre, el atacante subió configuraciones de CI manipuladas que hicieron caer procesos worker de GitLab en ocho ocasiones; el ataque principal del día 21 también provocó errores 502 y mató trabajos de CI. En cuatro de esas caídas, según Alpine, el atacante llegó a medio camino del exploit y falló en el paso siguiente.

El dato que debería acelerar el pulso de cualquier defensor: durante la campaña, el atacante descargó la imagen de contenedor de GitLab de la propia Alpine. Eso no es un escaneo a ciegas; es alguien que construye una réplica local del objetivo para afinar desplazamientos y el comportamiento de la carga útil. La lectura de Alpine, citada por The Hacker News:

«sugiere que estaban ajustando el exploit a nuestra compilación exacta», dijo Alpine sobre la decisión del atacante de descargar su imagen de contenedor de GitLab en mitad del ataque.

Alpine bloqueó la cuenta el 23 de octubre, el mismo día en que Ruby upstream publicó la corrección en las versiones 3.3.13, 3.4.12 y 4.0.9, pero el atacante volvió los días 25 y 26 e intentó recuperar el acceso. Esos intentos fracasaron. El 26 de octubre Alpine hizo el descubrimiento que replanteó todo el incidente: el servidor seguía vulnerable pese a la actualización a GitLab 19.3.6 del 22 de octubre. El día 27 cerró GitLab a todos excepto a los desarrolladores y los servidores de compilación, inició una reconstrucción completa y la terminó el día 28, con todas las contraseñas restablecidas y todas las claves secretas de GitLab reemplazadas. Alpine no encontró evidencia de que el código del atacante llegara a ejecutarse, pero afirma que no puede descartarlo, y por eso trató la instancia como comprometida solo con esa base. Es la decisión correcta, y la que la mayoría de organizaciones se habría autoconvencido de no tomar.

El parche que no era tal: la versión de tu GitLab te engaña

Aquí está la trampa, y es la parte de esta historia con mayor alcance. La corrección del 12 de octubre no cambia el código propio de GitLab, sino que parchea el Ruby que GitLab incluye dentro de sus paquetes oficiales para Linux y sus imágenes de contenedor. Si instalaste GitLab desde esos artefactos oficiales y actualizaste, estás cubierto. Pero si ejecutas GitLab sobre tu propio Ruby, compilado desde código fuente o integrado en una imagen de contenedor personalizada como la de Alpine, actualizar GitLab no cambia nada. El componente vulnerable está debajo de la aplicación que acabas de parchear. La actualización a 19.3.6 de Alpine pareció una remediación durante cuatro días mientras la puerta seguía abierta.

Hay una segunda trampa, más silenciosa, en la forma en que Alpine distribuyó su propia corrección. Su imagen alpinelinux/gitlab:19.3.6 publicada antes del 27 de octubre usaba el Ruby estándar y es vulnerable; el mismo tag republicado el 27 de octubre contiene Ruby 3.3.13. La instrucción de Alpine es volver a descargar alpinelinux/gitlab:19.3.6 y considerar vulnerable cualquier descarga anterior al 27 de octubre, además de todos los tags antiguos. Reutilizar un tag mutable para una corrección de seguridad significa que quien fija versiones por tag, o usa una descarga en caché, puede estar ejecutando la compilación vulnerable creyendo que está parcheado. Comprueba los digests y las fechas de descarga, no los nombres de los tags. Y alejando aún más el zoom: el fallo está en el motor de expresiones regulares de Ruby, así que cualquier programa que compile expresiones regulares a partir de entrada no confiable está expuesto, no solo GitLab. Tus otros servicios en Ruby merecen esta misma revisión de versión esta semana.

Ilustración de claves y tokens antiguos fundiéndose mientras se forjan otros nuevos, que simboliza la rotación de credenciales
Cuando un servidor GitLab podría estar comprometido, el parche es la parte barata: rotar todo lo que guardaba es la factura real.

Lo que vale un servidor GitLab comprometido

El informe de Alpine incluye algo poco habitual en la divulgación de incidentes: una lista explícita de lo que habría quedado expuesto si el exploit hubiera tenido éxito. Merece leerse como lista de comprobación para tu propia instancia: direcciones de correo, nombres y direcciones IP de todos los usuarios; hashes de contraseñas; secretos de 2FA; variables de CI/CD y tokens de runners; credenciales de webhooks, integraciones y push mirrors; tokens de acceso personal (almacenados como hash); repositorios privados; e incidencias y merge requests confidenciales. Vuelve a leer esa lista como atacante. Los tokens de runners y las variables de CI/CD son las llaves de tu infraestructura de compilación; los webhooks y los push mirrors son caminos hacia cada sistema con el que GitLab se comunica; los repositorios privados son el producto. Por eso el cálculo de coste de remediación ante un posible compromiso de GitLab no es «aplicar el parche», sino «rotar todo». Alpine, de hecho, reemplazó las claves secretas de GitLab y restableció todas las contraseñas, aunque, como señala The Hacker News, no ha dicho si se rotaron los tokens de runners, las variables de CI/CD, los tokens de acceso o las credenciales de integración.

Dos decisiones de arquitectura evitaron a Alpine una semana mucho peor. Primero, las claves que firman los paquetes y las versiones de Alpine nunca se almacenaron en el servidor GitLab, así que el único secreto que habría convertido esto en una emergencia de cadena de suministro a nivel de distribución simplemente no estaba allí para robarse. Segundo, la verificación no dependió del sistema posiblemente comprometido: cada commit de aports entre el 20 y el 22 de octubre llegó mediante un push registrado de un desarrollador conocido, y se comprobó contra registros a los que el atacante no podía acceder (el clon de un desarrollador y los 45 servidores de compilación). Además, cada paquete compilado entre el 20 y el 24 de octubre, así como las versiones del 21 de octubre (3.21.9, 3.22.7, 3.23.7 y 3.24.3, incluidos los tarballs minirootfs de las imágenes oficiales de Docker), se remontó a commits tal como se publicaron en git.alpinelinux.org. Alpine está ampliando las comprobaciones hasta el 27 de octubre, cuando cerró el servidor. Confía, pero verifica, con la verificación anclada fuera del radio de explosión.

La detección es cara o cruz: caza las caídas y arregla el registro

Ahora, la parte incómoda. La única razón por la que Alpine vio este ataque es que el exploit seguía fallando. Ocho caídas de workers, errores 502 y trabajos de CI muertos son una señal de detección con la sutileza de una alarma de humo, pero un exploit que funciona es silencioso, y Alpine lo dice sin rodeos:

«Un exploit exitoso no dejaría ninguna caída ni rastro en los registros web», dijo Alpine, explicando por qué reconstruyó el servidor pese a no encontrar evidencia de ejecución de código.

Por si fuera poco, parte de las pruebas que podrían haber resuelto la cuestión ya no existe: los registros de trabajos en segundo plano y el sistema de archivos del contenedor antiguo han desaparecido, y ni Alpine ni su proveedor de alojamiento registraron las conexiones de red salientes, así que incluso una exfiltración de datos exitosa sería invisible a posteriori. Si de este incidente te llevas una sola lección más allá del parche, que sea esta. No puedes investigar retroactivamente una telemetría que nunca recopilaste.

Concretamente, este es el orden de operaciones que seguiría esta semana, reflejando los cambios que Alpine ha comprometido en su propia respuesta:

  • Confirma que el Ruby sobre el que corre GitLab es 3.3.13, 3.4.12, 4.0.9 o posterior, especialmente en compilaciones desde código fuente e imágenes personalizadas, donde actualizar solo GitLab deja el fallo abierto.
  • Busca en los registros de caídas de workers, picos de 502 y trabajos de CI fallidos correlacionados con cuentas recién registradas y configuraciones de CI subidas recientemente; trata las caídas repetidas como un intento de explotación, no como inestabilidad.
  • Si no puedes descartar la ejecución de código, rota la lista completa: tokens de runners, variables de CI/CD, tokens de acceso personal, credenciales de webhooks, integraciones y push mirrors, y contraseñas de usuarios.
  • Activa el registro de conexiones salientes y de base de datos en el host de GitLab y amplía la retención muy por encima de 14 días; la carencia de Alpine fue no tener ningún registro de egreso.
  • Mantén las claves de firma de paquetes y versiones fuera del servidor GitLab de forma permanente, y firma las etiquetas de versión para que los artefactos puedan verificarse de forma independiente de GitLab.
  • Si usas la imagen alpinelinux/gitlab, vuelve a descargar la 19.3.6; las imágenes descargadas antes del 27 de octubre y todos los tags anteriores son vulnerables.

El listado de CISA en KEV —la segunda entrada relacionada con CI/CD que exige este tipo de respuesta tras la SQLi en Arista VeloCloud— llega con un plazo del 1 de noviembre para las agencias federales, y el incidente de Alpine es la prueba de por qué todos los demás deberían moverse al mismo ritmo. Lo confirmado: el fallo es un RCE no autenticado con CVSS 10.0, la corrección existe en los paquetes oficiales de GitLab y en el Ruby upstream, y un atacante real lanzó un intento de explotación decidido y ajustado al objetivo contra un proyecto de código abierto de alto valor. Lo afirmado: Alpine no encontró evidencia de ejecución de código ni de manipulación de paquetes o versiones, verificado contra registros externos al sistema comprometido. Lo desconocido, y que seguirá siéndolo, es si alguno de esos cuatro intentos a medio camino, o alguno más silencioso, llegó a completar la cadena. La respuesta de Alpine a esa incertidumbre fue reconstruirlo todo y restablecer cada credencial. Si tu GitLab guarda tu código fuente, los secretos de tus pipelines y tu infraestructura vinculada a la firma, eso no es paranoia. Es el precio de entrada.

Fuentes

Este artículo se basa en informaciones y avisos de: