
Analisi della causa principale, laboratorio Docker vulnerabile e script PoC per CVE-2026-85706, una lettura arbitraria di file non autenticata in GitLab tramite differenziale del parser.
CVSS 10.0 · Non autenticata · Attivamente sfruttata (CISA KEV)
Una differenza di parsing tra GitLab Workhorse (reverse proxy in Go) e Puma/Grape
(Ruby) consente a un attaccante non autenticato di aggirare il passaggio di consegne
dell'upload accelerato di Workhorse e di raggiungere tre endpoint di upload con un
file.path controllato dall'attaccante, ottenendo una lettura arbitraria di file
sull'host GitLab.
Codificare un carattere di files come %66iles fa sì che la rotta di upload di Workhorse
non trovi corrispondenza (essa confronta sul percorso codificato) mentre Rails lo
decodifica e raggiunge il vero handler (instrada sul percorso decodificato). L'handler si
fida del parametro grezzo file.path che Workhorse avrebbe dovuto sovrascrivere — quindi
file.path=/etc/passwd viene letto dal disco senza credenziali.
POST /api/v4/projects/1/repository/%66iles/x?file=&file.path=/etc/passwd&file.size=1
^^^^^^ Workhorse misses -> raw file.path survives to Rails
| Versione | |
|---|---|
| Affette | CE/EE 18.7 → 19.1.8, 19.2 → 19.2.6, 19.3 → 19.3.2 |
| Corrette | 19.1.8 / 19.2.6 / 19.3.2 (2026-09-10) |
| Percorso | Contenuto |
|---|---|
docs/ANALYSIS.md | Analisi completa della causa radice — differenza di parsing, i due JWT di Workhorse, il bug di fiducia nel file.path grezzo, il diff della patch e il canale di esfiltrazione tramite errore riflesso. Analisi effettuata sul codice sorgente reale (v19.3.1-ee vs v19.3.2-ee). |
lab/ | Laboratorio vulnerabile basato su Docker (gitlab-ce:19.3.1-ce.0) + istruzioni di avvio. |
poc/ | detect.sh (oracolo di esistenza non distruttivo) e exploit.sh (lettura di file tramite errore riflesso). Singolo target, subordinato all'autorizzazione. |
Non hai familiarità con le interiora di GitLab? Ecco cosa fa ogni livello — nell'ordine in
cui una richiesta li attraversa. Un glossario più approfondito con i concetti chiave è in
docs/ANALYSIS.md §0.
Internet
│
▼
┌────────┐ ┌───────────┐ ┌────────┐ ┌──────────────────────┐
│ NGINX │────▶│ Workhorse │────▶│ Puma │────▶│ Grape / Rails app │
│(proxy) │ │ (Go) │ │ (Ruby) │ │ (Ruby) │
└────────┘ └───────────┘ └────────┘ └──────────────────────┘
| Componente | Cos'è |
|---|---|
| NGINX | Reverse proxy più esterno. Termina il TLS, serve i file statici, inoltra tutto il resto verso l'interno. Non direttamente coinvolto in questa vulnerabilità. |
| Workhorse | Un reverse proxy in Go specifico per GitLab. Il suo compito principale è scaricare il lavoro in cui Ruby è lento — specialmente lo streaming di upload di grandi dimensioni. Per gli endpoint di upload, Workhorse bufferizza il corpo in un file temporaneo, firma un JWT e riscrive file.path in modo che Ruby non veda mai i byte grezzi dell'upload. Inoltre appone un JWT Gitlab-Workhorse-Api-Request per ogni richiesta che inoltra (dimostrando "questo è passato attraverso il proxy", non "questo utente è autenticato"). |
| Puma | Il server applicativo Ruby che esegue l'app Rails. Riceve le richieste da Workhorse, esegue il middleware (Rack) e le smista al router. |
| Rack | Il livello di interfaccia del web server Ruby. Il middleware Rack gestisce il parsing della query string, la gestione delle sessioni e — cosa critica qui — la verifica del JWT di upload di Workhorse e la costruzione degli oggetti UploadedFile. Rack::Utils.parse_nested_query è la funzione i cui messaggi di errore fanno trapelare il contenuto dei file in questo exploit. |
| Grape | Un framework per API REST usato da GitLab per tutti gli endpoint /api/v4/*. Fornisce definizioni di rotte e before-filter come require_gitlab_workhorse! (controllo del proxy) e authenticate! (controllo dell'identità dell'utente). Gira dentro Rails, su Puma, dietro Workhorse — quindi vede il percorso URL decodificato. |
| Rails | Il framework web complessivo (Ruby on Rails). GitLab è un monolite Rails — modelli, servizi e middleware girano tutti qui dentro Puma. |
La vulnerabilità risiede nel divario tra Workhorse (che confronta le rotte sul percorso codificato) e Grape/Puma (che instradano sul percorso decodificato). Vedi sotto.
r.URL.EscapedPath() (codificato);
Puma/Grape instradano sul percorso decodificato. %66iles ≠ regex files per Workhorse, ma
si decodifica in files per Rails.file.path in un percorso temporaneo firmato e non imposta mai
l'header Gitlab-Workhorse-Multipart-Fields — ma inoltra comunque la richiesta (con un
JWT Gitlab-Workhorse-Api-Request valido).authenticate!, e l'handler
leggeva params['file.path'] direttamente (la stringa dell'attaccante) invece dell'UploadedFile
verificato da Workhorse e confinato al percorso params[:file].Rack::Utils.parse_nested_query
("invalid %-encoding (<file bytes>)") — la risposta riflette i byte del file
fino al primo % non valido.La patch aggiunge authenticate!, passa all'UploadedFile verificato e smette di
riflettere e.message. Vedi docs/ANALYSIS.md §5.
# 1. Stand up the vulnerable lab (see lab/README.md for details)
cd lab && docker compose up -d # wait ~5 min for GitLab to become healthy
# 2. Non-destructive detection
../poc/detect.sh http://localhost:8929
# 3. File-read PoC against a file you're authorized to read on your own lab
../poc/exploit.sh http://localhost:8929 /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
Questo è pubblicato per scopi difensivi ed educativi: comprendere, rilevare e applicare la patch a una CVE divulgata e corretta che è nella lista CISA KEV. Gli script qui presenti sono a singolo target e richiedono di specificare esplicitamente il target.
localhost. Non puntarlo verso host di terze parti.Se esegui GitLab, aggiorna a una versione corretta — è l'unico rimedio reale.
gitlab-org/gitlab @ v19.3.1-ee vs v19.3.2-eeMIT — solo analisi e codice PoC. GitLab è un marchio di GitLab Inc.; questo repository non è affiliato né approvato da GitLab Inc.