La instancia de GitLab que leyó sus propios secretos: un path traversal 10.0 con una ventana de veinte horas
La instancia de GitLab que leyó sus propios secretos
El 10 de septiembre de 2026, GitLab publicó correcciones de seguridad para 18 vulnerabilidades. Una de ellas, el CVE-2026-85706, GitLab la puntuó con CVSS 3.1 base **10.0 Crítico** y vector `AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N`. Es un path traversal en la API de commits de repositorio de GitLab autoalojado, y bajo ciertas condiciones un usuario no autenticado podía leer ficheros arbitrarios del servidor GitLab.
Vuelve a leerlo. No "un usuario podía leer ficheros que no le tocaban". Un usuario **no autenticado** podía leer **ficheros arbitrarios del servidor**, con una única condición previa: que la instancia tuviera al menos un proyecto legible de forma anónima. Un proyecto público. Toda la condición. Sin cuenta, sin token, sin interacción del usuario, sin phishing, sin malware, sin segundo paso.
En unas veinte horas desde la salida del parche, los laboratorios de watchTowr detectaban escaneo masivo de internet contra este CVE. CISA lo añadió al catálogo Known Exploited Vulnerabilities el 11 de septiembre con fecha límite el 14 para las agencias civiles federales, y marcó la triaje forense como obligatoria. Y el 14 de septiembre, un administrador de un GitLab autoalojado describió en el foro oficial de GitLab que había encontrado en los logs de su propia instancia que un atacante había accedido a su `gitlab-secrets.json` antes de que parcheara.
Esta es una historia de operación más que de tecnología, y la parte técnica es la fácil.
Cómo funciona, en breve
El mecanismo es un malentendido entre dos capas de GitLab sobre qué significa una petición. En las subidas multipart, Workhorse —la capa en Go delante de Rails— almacena el cuerpo en disco y le pasa a Rails parámetros generados: `file.path` para el cuerpo subido y `metadata.path` para un fichero de metadatos. Rails abre la ruta que le indique el llamante. Dos defectos se combinan: la ruta no está confinada al directorio del repositorio, y no se exige autenticación antes de esa lectura.
Los exploits públicos llegan codificando en URL un solo carácter de la palabra "commits": `%63ommits`. La sanitización de rutas de Workhorse no ve la ruta codificada, así que nunca sobrescribe `file.path`; Rails sí la decodifica y despacha al handler, que acepta la ruta desde la query string. El endpoint es `POST /api/v4/projects/:id/repository/commits`, y análisis independientes reprodujeron el mismo comportamiento en la API de ficheros del repositorio mediante POST y PUT.
Hay un segundo canal, más silencioso, para los datos. Con `Content-Type: application/x-www-form-urlencoded`, el helper de subida vuelve a parsear el contenido del fichero, y un signo de porcentaje que no va seguido de dos dígitos hexadecimales se refleja en el cuerpo de la respuesta HTTP 400. Los ficheros sin porcentaje inválido solo producen un oráculo de existencia 401 — el problema con `gitlab-secrets.json`, que es hexadecimal y base64 de arriba abajo.
La primitiva de lectura se ejecuta con los permisos del proceso de servicio de GitLab, generalmente el usuario `git`. Así que el conjunto alcanzable es todo fichero que esa cuenta pueda leer: `gitlab.yml`, `secrets.yml`, `gitlab-secrets.json`, `database.yml`, claves privadas SSH, certificados `.pem`, ficheros `.env`, tokens de runner y de despliegue, variables de CI/CD, logs de aplicación, más rutas de reconocimiento como `/etc/passwd` o `/proc/self/environ`.
Por qué una fuga de secretos es peor que una fuga de información
Aquí es donde el incidente cambia de categoría, y es la parte en la que conviene que se detengan los comités de dirección.
Una lectura de ficheros sin autenticación en una instancia de GitLab es grave. Pero una instancia de GitLab no es un almacén de documentos: es el sitio donde viven los secretos de tu cadena de suministro de software. Si el atacante leyó `gitlab-secrets.json` o `secrets.yml`, no se limitó a aprender algo sobre tu infraestructura: obtuvo la `secret_key_base` de Rails y las claves de cifrado.
Con la secret key base, un atacante puede **forjar sesiones y tokens** y **descifrar las variables de CI/CD almacenadas y los datos de autenticación multifactor**. Las variables de CI/CD son las reliquias de la mayoría de las organizaciones de ingeniería: credenciales de registro, claves de nube, claves de firma, tokens de pasarelas de pago, credenciales de despliegue. Están guardadas cifradas, y la clave de ese cifrado estaba en un fichero al que llegaba una petición HTTP anónima.
Desde ahí el pivote es mecánico. Tokens de despliegue, tokens de acceso personal, claves de nube y credenciales de registro dan al atacante movimiento hacia cuentas cloud, registries de contenedores y pipelines. Eso es exposición de cadena de suministro de software aguas abajo de la instancia de GitLab, y es la parte que convierte un incidente de infraestructura en una pregunta sobre tus productos.
La disponibilidad no se ve afectada: el vector CVSS puntúa el impacto en disponibilidad como None, y este es un evento de confidencialidad e integridad, no ejecución remota de código directa. También eso conviene anotarlo, porque es exactamente el tipo de incidente que se degrada en un registro de riesgos: nada está caído, nada se ve roto, y la pérdida es entera en cosas que se copiaron en silencio.
La ventana de veinte horas, y lo que dice de tu proceso de parcheo
La cronología es la lección operativa:
| Fecha | Suceso | |---|---| | 2026-09-04 | CVE reservado | | 2026-09-10 | Salen las correcciones (19.3.2, 19.2.6, 19.1.8) | | 2026-09-11, ~06:00 UTC | watchTowr Labs detecta escaneo masivo de internet | | 2026-09-11 | Entrada en KEV de CISA, plazo el 14 de septiembre | | 2026-09-12 | Publicación del CVE, EPSS 11,1% (percentil 96) | | 2026-09-14 | Vencimiento federal; primeros casos confirmados en el foro de GitLab | | 2026-09-23 | Backports a 19.0.9 y 18.11.12; se actualizan los rangos afectados |
Veinte horas. Ese es el hueco entre que el parche está disponible y que internet entero busca una forma de entrar. Un cuarto de día no es mucho tiempo para descubrir que tu instancia autoalojada es una instancia de GitLab, abrir una ventana de mantenimiento y ejecutar una actualización que —y esto importa operativamente— **incluye migraciones de base de datos**. Las instalaciones de nodo único tienen parada mientras se ejecutan esas migraciones; los despliegues multinodo pueden usar el procedimiento de actualización sin downtime de GitLab, y de las tres versiones corregidas solo 19.3.2 lleva migraciones posteriores al despliegue.
Pregúntate con honestidad: si tuvieras que llevar una instancia de GitLab autoalojada a una versión corregida en 24 horas, ¿podrías? No "te gustaría" — ¿podrías, con la ventana de cambio que tienes, sobre la instancia que tienes, en la rama que mantienes? La mayoría de las organizaciones no pueden responder eso sobre su propio GitLab antes de que se lo pregunten. Las que sí, no pasaron una mala semana.
Hay un segundo detalle del calendario que debería informar cómo planificas. GitLab publicó las correcciones iniciales para 19.3, 19.2 y 19.1 el 10 de septiembre. Los backports a **19.0.9 y 18.11.12 no salieron hasta el 23 de septiembre**, dos semanas después, por ser ramas en mantenimiento. Si tú estabas en 18.11, el parche que podías aplicar el día de la publicación todavía no existía. Las ramas con ventana de soporte larga son exactamente las ramas donde viven las empresas, y exactamente las que tardan más en corregirse. Cualquier proceso que dé por hecho que "el fix ya está" está equivocado durante dos semanas de cada mes.
La pregunta que el vulnerabilidad te está haciendo
Aquí está el hallazgo incómodo. **La condición previa de este ataque es que tu instancia tenga al menos un proyecto público.** Eso no es un fallo: es una decisión de producto, porque un proyecto open source necesita acceso anónimo de lectura a su repositorio, y GitLab lo soporta perfectamente bien.
Lo que significa es que el control técnico que habría evitado la explotación era una funcionalidad del producto funcionando como se espera, y el control administrativo que la habría evitado fue saber cuáles de tus instancias tienen un proyecto público.
Para la mayoría de las organizaciones, la respuesta honesta a "¿tenemos proyectos públicos en nuestro GitLab autoalojado?" es que nadie lo ha preguntado en mucho tiempo. Y esa pregunta no es un examen de seguridad: un flujo de contribución a un proyecto abierto, un repositorio público de documentación, una página de estado de cliente en la misma instancia, un mirror de algo público — cualquiera basta. Todos son razonables, y todos son un camino válido a leer los secretos de tu instancia.
GitLab se describe como una plataforma DevOps con más de 30 millones de usuarios registrados y presencia en más del 50% de las Fortune 100. CISA ha marcado cuatro vulnerabilidades suyas como explotadas desde noviembre de 2021, dos solo en febrero de 2026, y el antecedente de 2023 (CVE-2023-2825) fue también un path traversal de severidad máxima explotado en pocos días. No es un caso aislado.
La pregunta estratégica para un CTO o una CISO no es, por tanto, "parchamos o no". Es: **¿cuál es el radio de impacto de una única petición HTTP no autenticada contra el sistema que sostiene nuestro pipeline de entrega, y sabríamos si ya ha pasado?**
Qué hacer en la primera hora, y qué hacer en la primera semana
La primera hora es para establecer si fuiste víctima. El propio GitLab publicó tres detecciones en su framework abierto TLDR —`local_file_inclusion_gitlab_yml_read.md`, `local_file_inclusion_metadata_path.md` y `local_file_inclusion_path_enumeration.md`—, documentadas para disparar sobre eventos de `api_json.log`. La lógica es: una petición POST cuyos parámetros contengan `file.path`, `file%2epath`, `metadata.path` o `metadata%2epath`, con `meta.client_id` empezando por `ip/` (llamante no autenticado), y un valor que no apunte a `/uploads/tmp/` ni a `/tmp/gitlab/` —el sandbox de subidas legítimo y el directorio temporal de Workhorse—.
``` # en la capa web, proxy o WAF: estos parametros nunca aparecen en una URL legitima grep -E "url\.path=.*/api/v4/projects/.*/repository/.*(file\.path|metadata\.path|file%2epath|metadata%2epath)" access.log ```
``` # en api_json.log del propio GitLab: cliente anonimo pidiendo rutas fuera del sandbox grep -E "\"meta\":\{[^}]*\"client_id\":\"ip/" api_json.log \ | grep -E "file\.path|file%2epath|metadata\.path|metadata%2epath" \ | grep -vE "/uploads/tmp/|/tmp/gitlab/" ```
La semántica del código de respuesta importa, y es contraintuitiva. **El HTTP 400 es el rechazo esperado** para esta forma de petición. Un 2xx del mismo llamante anónimo en la misma ruta significa que el fichero puede haber sido servido — una lectura confirmada. Otros estados siguen confirmando que el parámetro es alcanzable y se está sondeando. Y cinco o más rutas distintas fuera del sandbox desde una misma dirección en una hora indican un barrido del sistema de ficheros.
Y a partir de ahí, la primera semana es de rotación, que es la parte que muchas organizaciones se saltan porque después del parche parece trabajo burocrático. Parchear frena las lecturas nuevas. No revoca el material que ya se copiaron. Y el material que se podía copiar de una instancia de GitLab es: credenciales de base de datos, claves SSH, deploy tokens, tokens de acceso personal, variables de CI/CD y claves de nube. Varios vendors, incluidos Rapid7 y Abstract Security, son explícitos en que hay que rotarlos. La entrada de KEV de CISA lleva requisitos de triaje forense bajo la Binding Operational Directive 26-04, que es la formulación federal de la misma instrucción: esto no se cierra con un número de versión.
Así que la secuencia es: actualizar a una release corregida, rotar las credenciales, y cazar en toda la ventana de exposición señales de compromiso —tokens de acceso personal y deploy keys creados o modificados, ejecuciones de pipeline inesperadas, registros nuevos de runner, autenticación SSH desde orígenes desconocidos, e inicios de sesión en cuentas cloud o de registro cuyas credenciales vivían en ese host—.
Y si no puedes actualizar de inmediato, las mitigaciones son poco glamurosas y efectivas: sacar la instancia de la exposición pública a internet o restringirla a rangos IP de confianza, auditar qué proyectos son públicos, y filtrar en el WAF el tráfico de la Commits API y la Repository Files API. GitLab no propuso ningún workaround propio: la única respuesta sancionada es la actualización.
La decisión que esta vulnerabilidad está poniendo a prueba
Este es el encuadre que llevaría a un consejo o a un comité de dirección, porque es la parte que no cabe en un ticket.
GitLab no es un software que le gusta a tus desarrolladores. Es el sitio donde tu organización guarda su capacidad de entrega: el código, el historial, la trazabilidad de revisión, los pipelines, las variables, los artefactos y el log de auditoría que te van a pedir tus auditores y tus reguladores. En un despliegue autoalojado, ese activo tiene exactamente una copia, reside en infraestructura que opera tu equipo, y es alcanzable —o no— según decisiones que tomaron personas que no tenían motivo para pensar en ello.
La pregunta de fondo del CVE-2026-85706 no era "sabes parchear GitLab". Era "¿sabes qué habría dado tu instancia de GitLab, y te enterarías?". Para muchísimas organizaciones, la respuesta fue: no, y no.
Eso se arregla este trimestre, y es mejor uso de las próximas dos semanas que leer otro aviso. Haz un inventario de todas las instancias de GitLab autoalojadas, incluidas las de filiales, la del laboratorio y la que desplegó un contratista y nunca entregó. Establece cuáles están expuestas a internet, cuáles tienen proyectos públicos, y cuál sería el camino de actualización y el coste de parada de cada una. Después decide —por escrito— si cada una se parchea, se aísla o se migra a una offering gestionada, y quién es el dueño de esa decisión.
Porque las organizaciones que responden a estas preguntas por adelantado son las que, en la próxima ventana de veinte horas, tienen un cambio programado en vez de un incidente.
Y en el contexto europeo hay una capa más: una fuga de `secret_key_base` y de las variables de CI/CD en una entidad que presta servicios a clientes sujetos a NIS2 no es solo un incidente técnico del proveedor de la plataforma. Es un incidente de la cadena de suministro de software de esa entidad, con consecuencias de notificación que se negocian peor cuando el primer dato que se tiene es que alguien leyó el fichero de secretos de producción.
**Si operas GitLab autoalojado: identifica hoy todas las instancias que son tuyas, revisa los logs de API buscando lecturas de rutas por parte de anónimos, y trata la rotación de credenciales como parte del incidente y no como tarea posterior. Y responde a una pregunta esta semana: si mañana tuviéramos que actualizar todas, ¿cuánto tardaríamos?**
---
*Fuentes: anuncio de seguridad de GitLab del 10 de septiembre de 2026 y la actualización de documentación del 23 de septiembre de 2026; registro del National Vulnerability Database de NIST para el CVE-2026-85706 (publicado el 12 de septiembre de 2026, modificado el 24 de septiembre de 2026); registro de la CVE List de MITRE (actualizado el 23 de septiembre de 2026); catálogo Known Exploited Vulnerabilities de CISA, versión 2026.10.23; watchTowr, detección en su red de honeypots Attacker Eye y advisory del 10 de septiembre de 2026, y detección de escaneo masivo del 11 de septiembre de 2026; Greenbone (17 de septiembre de 2026); Abstract Security (17 de septiembre de 2026); análisis de PoC de EQSTLab y su laboratorio público vulnerable; Truesec (29 de septiembre de 2026); Rapid7 (14 de septiembre de 2026); Orca Security (14 de septiembre de 2026); SOC Prime (14 de septiembre de 2026); SecurityAffairs (13 de septiembre de 2026); InfoQ (3 de octubre de 2026); BleepingComputer; hilo 135308 del foro de GitLab, 14 de septiembre de 2026; reglas de detección TLDR de GitLab; informe 3909881 de HackerOne y work item 627748 de GitLab.*