Volver al Blog

«Solo lectura» no existe: GitLab le pone un 10,0 a un fallo que solo sabe leer

Estanterías de un depósito de archivo con legajos y cajas de documentos etiquetados

«Es solo lectura» es la frase con la que se aprueban cosas sin reunión. El token de solo lectura para el script. La cuenta de solo lectura para el consultor. El rol de lector en el tenant, que se concede por chat en treinta segundos. Como no escribe, no rompe nada; como no rompe nada, no hay que discutirlo con nadie. El jueves 10 de septiembre GitLab publicó el parche de un fallo que solo sabe leer ficheros y le puso un 10,0. La nota máxima, y con el número lo firmaron ellos.

El calendario de la semana va así. Jueves 10: GitLab saca las versiones corregidas. Viernes 11: la empresa de seguridad watchTowr informa de sondeos contra ese fallo en instancias expuestas a Internet. Ese mismo viernes, CISA lo mete en su catálogo de vulnerabilidades explotadas con fecha límite el lunes 14. Y hasta la madrugada del sábado 12 no aparece la ficha publicada en la base de datos nacional de vulnerabilidades, que es de donde beben buena parte de los inventarios de parcheo. La lectura es nuestra, pero cuesta esquivarla: el escáner que sincroniza con el NVD se enteró después que quien ya estaba probando la puerta.

Nosotros tenemos un GitLab autoalojado, con el CI/CD GitOps desde el que se despliega esta misma web, así que el aviso nos tocaba de primera mano. Lo que sigue no es la crónica de nuestra instancia —eso se queda en casa—, sino el orden en el que miramos estas cosas cuando aparece un número redondo: primero qué se rompió, después qué dice el vector, y al final la pregunta que no cierra el parche.

Qué se rompió, con nombres y números

El fallo es CVE-2026-85706 y afecta a GitLab Community Edition y Enterprise Edition. La descripción oficial, traducida, dice que bajo determinadas condiciones un usuario no autenticado podría haber leído ficheros arbitrarios del servidor GitLab, por confinamiento incorrecto de rutas y falta de aplicación de la autenticación en la API de commits del repositorio. Dos defectos, no uno: la ruta no estaba bien acotada y el endpoint no comprobaba quién preguntaba.

Las versiones afectadas son todas desde la 18.7 hasta antes de la 19.1.8, la rama 19.2 antes de la 19.2.6 y la 19.3 antes de la 19.3.2. Esas tres son las corregidas, publicadas el 10 de septiembre en un paquete con dieciocho arreglos de seguridad. Lo encontró alguien de fuera, a través del programa de recompensas: en las referencias del CVE figuran el informe de HackerOne y el ticket interno de GitLab. La clasificación del defecto baila según quién la rellene —GitLab, que es quien mantiene el registro del CVE, lo cataloga como CWE-22, y CISA en su catálogo como CWE-35—, y las dos son variantes de lo mismo: recorrido de rutas.

La prueba de concepto completa no es pública, pero los indicadores para buscar en tus registros sí lo son desde el viernes, y conviene apuntarlos porque cambian todo lo que viene después. watchTowr publicó que hay que cazar peticiones HTTP POST contra rutas del tipo /api/v4/projects/{id}/repository/commits/ que lleven parámetros file.path. Y añadió la condición previa que la descripción oficial deja en «bajo determinadas condiciones»: hace falta que en la instancia exista al menos un proyecto público.

Ese detalle del método es la mejor noticia de toda la semana para quien tenga que revisar. La ruta de commits la consultan a diario integraciones, generadores de insignias y paneles de despliegue, y todas ellas hacen GET. Un POST sin usuario contra esa ruta no se parece al tráfico de nadie: es una anomalía con forma reconocible, y por tanto algo que se puede buscar en un registro sin ser analista forense.

El 10,0 no lo pone el fallo. Lo pone el vector

Una puntuación CVSS sale siempre de un vector, y el de este CVE lo firma [email protected], o sea el propio fabricante. Es éste:

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N — 10.0

Vector CVSS 3.1 de CVE-2026-85706 según el registro del CVE, fuente [email protected].

La primera mitad es la que todo el mundo espera de un fallo grave: se ataca por red (AV:N), sin que el atacante tenga que preparar nada (AC:L), sin credenciales (PR:N) y sin que nadie tenga que pinchar en nada (UI:N). Cuatro ceros de fricción. La segunda mitad es la interesante. C:H es lo esperable: se lee todo. Pero al lado hay I:H, impacto alto en integridad, en un fallo que no escribe nada. Y delante, S:C: cambio de alcance, que en el vocabulario de CVSS significa que el daño sale del componente vulnerable y cae en otro sitio.

La cuenta la puede repetir cualquiera en la calculadora oficial. Coge el mismo vector y déjalo en lo que un fallo de lectura parece a simple vista: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N. Eso puntúa 7,5. El fallo es el mismo, la red es la misma y la facilidad es la misma. Los 2,5 puntos que separan una cifra de la otra describen lo que hay guardado dentro de los ficheros, y los pone ahí el fabricante en el único idioma formal que tiene para decirlo: en un servidor GitLab, leer no se queda en leer.

En el mismo paquete de parches del día 10 hay un segundo crítico que sirve de contraste. CVE-2026-87719 puntúa 9,9: una deserialización insegura en un serializador de suscripciones GraphQL que permite sacar configuraciones de la búsqueda avanzada y credenciales sensibles. Es una décima menos, y aun así está mucho más lejos: su vector lleva PR:L y la descripción pide un usuario autenticado con acceso a Duo Chat, además de ser solo Enterprise Edition. Para el 9,9 hace falta alguien dentro. Para el 10,0 no hace falta nadie.

«Solo lectura» es el permiso que se aprueba sin reunión

El mismo razonamiento que hace que este CVE valga un 10,0 se aplica, sin cambiar una coma, a media docena de decisiones que se toman en cualquier empresa sin llamarlas decisiones. El token «de solo lectura» del CI que puede clonar los ciento veinte repositorios, incluido el que tiene el .env de producción que alguien subió en 2023. La cuenta «de solo lectura» que se le da al consultor para la auditoría y que ve la tabla entera de clientes. El rol de lector del directorio, que no toca nada y a cambio te enseña el organigrama completo, los grupos y quién es administrador: el mapa exacto que necesita alguien para escribir un correo creíble.

Ya escribimos sobre un caso de manual de esta familia: el privilegio REPLICATION de PostgreSQL, que se concede porque suena a copia y no a escritura, y que en la práctica te entrega el clúster entero. El patrón es idéntico, y el fallo está en el vocabulario antes que en el software. Lo que hace peligroso a un permiso no es el verbo, es el alcance. «Lectura» describe la acción; nadie pregunta por el complemento directo.

No estamos diciendo que leer y escribir sean lo mismo; sería una tontería y además es falso. Estamos diciendo algo más incómodo y más útil: en cualquier sistema que guarde credenciales para hablar con otros sistemas, la distancia entre leer y escribir es un copiar y pegar. Y un servidor de código autoalojado es, por definición, un sistema que guarda credenciales para hablar con otros sistemas. Ése es su trabajo. Por eso ese S:C del vector es literal y no retórico.

CISA no pidió solo parchear. Pidió mirar atrás

El catálogo de vulnerabilidades explotadas de CISA es un fichero JSON público, y merece la pena bajárselo en vez de leer lo que otros cuentan de él, porque cada entrada trae campos que la cobertura de prensa no suele mirar. La de GitLab entró el 11 de septiembre con fecha límite el 14 —tres días— y con un campo puesto a «Yes» que se llama forensicTriage: triaje forense.

Contamos cuántas entradas llevan esa marca, porque el número dice más que la etiqueta: 51 de 1.709. Un tres por ciento del catálogo. Y el plazo tampoco es el de todos: ese mismo 11 de septiembre entraron dos fallos de JFrog Artifactory con catorce días y sin marca de triaje. Mismo día, misma agencia, casi cinco veces más margen. Que no es cuestión de fabricante lo enseña la tercera alta de la jornada: ConnectWise ScreenConnect, también con tres días y también con la marca puesta. El catálogo está distinguiendo, entrada por entrada, entre «ponte al día» y «ponte al día y además comprueba si ya pasó». La segunda sale cara, porque no la resuelve un apt upgrade.

Esa directiva obliga a las agencias federales de Estados Unidos y a nadie más; desde luego, no a una empresa de Sant Fruitós de Bages. Ninguna pyme española está incumpliendo nada por no haber parcheado el domingo. Pero el criterio de priorización es público y es gratis, y el criterio dice que este fallo pertenece a la categoría de los que hay que investigar hacia atrás. La misma acción requerida de la ficha incluye una frase que sí aplica a todo el mundo, y con la que estamos de acuerdo: los responsables deben evaluar la exposición a Internet de cada activo. Evaluar. No suponer.

«¿Nos leyeron?» tiene respuesta, y el fichero lleva ahí desde el primer día

Un fallo de lectura por HTTP deja rastro. No el rastro que busca un antivirus, porque no hay proceso nuevo ni binario caído, pero sí una línea en un registro. En una instalación con el paquete oficial de GitLab, las llamadas a la API se escriben en /var/log/gitlab/gitlab-rails/api_json.log, una línea JSON por petición, con estos campos, entre otros: time, status, method, path, params, remote_ip, route, user_id y username. Al lado, el servidor web guarda su propio registro en /var/log/gitlab/nginx/gitlab_access.log.

Y hay una segunda buena noticia, ésta por defecto: la rotación que trae de fábrica el paquete es diaria, guarda 30 rotaciones y comprime. Traducido: si nadie ha tocado esa configuración, tienes alrededor de un mes de historial en disco, que cubre de sobra el 10 y el 11 de septiembre. No hay que comprar nada para responder a la pregunta. Hay que abrir el fichero.

Como la ruta se guarda normalizada con el patrón /api/:version/..., se puede buscar por el tramo del endpoint sin saberse el identificador de ningún proyecto. Con los indicadores de watchTowr —método POST y parámetro file.path— el barrido queda bastante estrecho. Sobre los registros rotados incluidos:

# 1. lo que watchTowr da como indicador: POST + file.path contra la ruta de commits
zcat -f /var/log/gitlab/gitlab-rails/api_json.log* \
  | jq -r 'select((.route // "") | test("repository/commits"))
           | select(.method == "POST")
           | select((.params // [] | tostring) | test("file\\.?_?path"; "i"))
           | [.time, .remote_ip, .status, (.username // "-")] | @tsv'

# 2. quien pregunta por esa ruta sin usuario, agrupado por IP para ver el volumen
zcat -f /var/log/gitlab/gitlab-rails/api_json.log* \
  | jq -r 'select((.route // "") | test("repository/commits"))
           | select(.user_id == null)
           | .remote_ip' \
  | sort | uniq -c | sort -rn | head -20

El primer comando busca el intento con la forma que describe watchTowr; el segundo dibuja el fondo, que es qué direcciones preguntan por esa ruta sin usuario y con qué volumen. Tres avisos antes de que alguien saque conclusiones. Si tu GitLab está detrás de un proxy o un balanceador, remote_ip puede ser el del proxy y hay que ir a la cabecera reenviada. Hay peticiones legítimamente anónimas contra proyectos públicos, así que la ausencia de usuario, por sí sola, no acusa a nadie. Y si al lanzar el comando descubres que solo tienes siete días de historial porque alguien recortó la rotación hace dos años, ése es tu hallazgo de hoy, por encima del CVE.

Y si de esa búsqueda sale algo, la conversación deja de ser técnica y pasa a ser de plazos: hace dos días escribíamos sobre las veinticuatro horas del Cyber Resilience Act y el momento exacto en el que arranca el reloj. Arranca cuando te enteras, no cuando se publica el CVE. Por eso el orden importa: primero se mira, y con lo que salga se decide. Al revés no se puede.

Cuándo no haríamos nada esta noche

Un 10,0 no significa «te van a entrar hoy». Significa que para intentarlo no hace falta ninguna credencial, que es lo que dice PR:N; es una medida de facilidad, y la probabilidad la pone tu exposición. De ahí salen dos recomendaciones que suenan contradictorias y no lo son. Si tu GitLab no contesta desde Internet —y lo has comprobado, no lo supones—, no montes una ventana de emergencia un domingo por la noche para una 19.1.x. Actualizar GitLab a lo loco un fin de semana, sin copia previa y sin nadie de guardia, tiene su propia manera de arruinarte el lunes. Ventana ordenada, copia antes, y a ser posible con alguien delante.

Dicho eso, dos matices. El primero: la copia previa tiene que ser una copia de verdad, y ya explicamos por qué clonar los repositorios no es tener copia de GitLab —faltan las incidencias, los merge requests, los registros y, muy en particular, el fichero de secretos sin el cual la base de datos que restauras no se puede descifrar—. El segundo: aunque no estés expuesto, mira el registro igual. Una lectura desde dentro también es una lectura, y la parte más incómoda de un compromiso no es el que ocurre, sino el que ocurrió hace tres semanas y nadie miró. Es la misma lección que sacábamos del incidente de Cisco FMC: el parche cierra la puerta, pero no contesta qué salió por ella antes.

Lo decimos aquí desde hace años con otras palabras: el fallo es inevitable, la avería es una decisión de diseño. Que exista un CVSS 10,0 en el software que usas no es tu culpa ni la vas a evitar. Que no puedas contestar «¿nos leyeron?» sí es una decisión, y se tomó el día que alguien decidió cuánto historial guardar y quién lo mira.

¿Quién mira tus registros el día que hace falta?

Con el alcance por delante: un agente en el servidor no te contesta esto, porque no hay proceso raro ni binario nuevo, hay un POST contra la API de commits. Lo que contesta es el registro guardado el tiempo suficiente y alguien que sepa qué buscar en él. Eso es la parte de respuesta de nuestro EDR/MDR gestionado: retención, correlación y una persona de guardia. Al lado va el Zero Trust, que es lo que hace que un «solo lectura» signifique de verdad poca cosa. Si al revisarlo resulta que lo tienes bien montado, te lo diremos y no habrá factura.

Hablar con everyWAN

Nota de fuentes

Fuentes primarias que hemos leído nosotros. El registro de CVE-2026-85706 en la base de datos nacional de vulnerabilidades del NIST: de ahí salen la descripción citada, el rango de versiones afectadas y corregidas (18.7 < 19.1.8, 19.2 < 19.2.6, 19.3 < 19.3.2), la clasificación CWE-22 y el vector —ambos aportados por [email protected] como responsable del registro, porque el NIST todavía no lo ha analizado: la ficha figura en estado «Received»—, la fecha de publicación de la ficha (12 de septiembre de 2026, 03:16 UTC) y el vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N con puntuación 10,0, atribuido a [email protected]; entre sus referencias figuran el informe de HackerOne y el work item de GitLab. La nota de la versión 19.3.2 de GitLab, del 10 de septiembre de 2026, para el paquete de dieciocho arreglos y para el segundo crítico, CVE-2026-87719 (9,9, solo Enterprise Edition), cuyo vector PR:L hemos comprobado también en el registro del NIST. El catálogo de vulnerabilidades explotadas conocidas de CISA, descargado en formato JSON (instantánea del 11 de septiembre de 2026, 19:32 UTC, con 1.709 entradas): de ahí salen la fecha de alta (11 de septiembre), la fecha límite (14 de septiembre), el campo forensicTriage en «Yes», el texto de la acción requerida y las entradas de JFrog Artifactory del mismo día con plazo de catorce días. La documentación de administración de GitLab para las rutas y los campos de api_json.log y gitlab_access.log, y su documentación del paquete Linux para los valores por defecto de rotación (diaria, 30 rotaciones, comprimida). El aviso de watchTowr del 11 de septiembre, del que tomamos tanto los sondeos observados como su guía de caza —peticiones POST a rutas /api/v4/projects/{id}/repository/commits/ con parámetros file.path— y la condición previa de que exista al menos un proyecto público en la instancia; esa guía la recogen también SecurityWeek y The Hacker News el mismo día. Que ConnectWise ScreenConnect (CVE-2026-84869) entrara el 11 con tres días y triaje forense sale del mismo JSON de CISA.

Lo que es cuenta nuestra o lectura nuestra, no de las fuentes. El recuento de 51 entradas con marca de triaje forense sobre 1.709 lo hemos hecho nosotros sobre el JSON descargado; cambia cada vez que CISA publica. La comparación entre el 7,5 y el 10,0 es aritmética nuestra con la fórmula estándar de CVSS 3.1: el vector hipotético AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N puntúa 7,5, y cualquiera puede reproducirlo en la calculadora oficial de FIRST. La interpretación de S:C e I:H como una afirmación del fabricante sobre el contenido de los ficheros es lectura nuestra, no una declaración de GitLab. También son nuestros: la tesis del vocabulario («lo que hace peligroso a un permiso no es el verbo, es el alcance»), la lectura del plazo de tres días frente al de catorce como una distinción entre parchear e investigar, la observación de que el escáner que sincroniza con el NVD se entera después que quien ya sondea, la de que un POST sin usuario contra la ruta de commits es una anomalía frente al GET de las integraciones, la de que un 10,0 mide facilidad y no probabilidad, y el cálculo de «alrededor de un mes de historial» a partir de los treinta ciclos diarios de rotación documentados, el comando de zcat y jq —que es un punto de partida, no una regla de detección— y sus tres avisos. Los ejemplos de permisos de solo lectura son ilustrativos y no describen a ningún cliente concreto. Que tengamos GitLab autoalojado con CI/CD propio sí es nuestro. Todo se comprobó el 13 de septiembre de 2026.

Fotografía de portada: «Documents on repository shelving at The National Archives», de The National Archives (Reino Unido), publicada en Wikimedia Commons bajo licencia Creative Commons CC BY 3.0. La hemos recortado y le hemos superpuesto los textos de marca.

Ciberseguridad GitLab Vulnerabilidades DevOps Zero Trust
Compartir LinkedIn X

Suscríbete a nuestra newsletter

Para recibir historias del mundo IT, novedades de everyWAN y ofertas exclusivas para suscriptores, date de alta a nuestra lista de correo

everyWAN
everyWAN