GitLab CVE-2026-85706: la lettura file non autenticata svuota i tuoi segreti CI/CD
Una path traversal CVSS 10 nell'API commits di GitLab fa leggere a chiunque secrets.yml, deploy token e variabili CI/CD — senza login, senza log di autenticazione.
La scadenza federale per applicare la patch di CVE-2026-85706 è oggi. Ed è la scadenza sbagliata da temere. GitLab ha rilasciato la correzione il 10 settembre; CISA ha inserito il bug nel suo catalogo Known Exploited Vulnerabilities l'11 settembre, con tre giorni di tempo per le agenzie federali. Ma watchTowr ha registrato sonde reali contro la falla a partire dalle 06:00 UTC dell'11 settembre — poche ore dopo la pubblicazione della patch, su istanze che non l'avevano ancora applicata. Se il tuo GitLab self-managed era esposto e non aggiornato in quella finestra, applicare la patch adesso chiude la porta su una cassaforte già vuota. I segreti che proteggeva sono già stati letti.
È questa la proprietà che rende CVE-2026-85706 peggiore di quanto suggerisca il suo punteggio CVSS 10.0: la vulnerabilità è una lettura arbitraria di file non autenticata. Non c'è login, quindi non c'è alcun evento di autenticazione da registrare. L'unica traccia del fatto che l'attaccante se ne sia andato con il tuo secret_key_base è la richiesta HTTP in sé — e la maggior parte dei team non conserva né ispeziona i corpi delle richieste all'API.
Cosa fa davvero CVE-2026-85706
La falla vive nell'API dei commit del repository. Un POST verso /api/v4/projects/{id}/repository/commits/ con un parametro file.path che contiene sequenze di path traversal spinge GitLab a restituire il contenuto di file arbitrari sul server — qualsiasi cosa leggibile dall'utente di servizio gitlab. La descrizione della causa radice fornita da GitLab è "confinamento del path improprio e mancata applicazione dell'autenticazione nell'API dei commit del repository". Due parole in quella frase fanno tutto il danno: mancata autenticazione. L'endpoint che normalmente richiede un token di progetto qui non richiede nulla.
C'è esattamente una precondizione, banale da soddisfare: deve esistere almeno un progetto pubblico sull'istanza. È la configurazione predefinita per la maggior parte dei deployment GitLab che ospitano un mirror open-source, un template o un repo di demo. La sintesi di watchTowr è netta — "lo sfruttamento richiede un solo requisito, deve esistere almeno un progetto pubblico".
Il range interessato è ampio. Ogni istanza self-managed Community ed Enterprise Edition è vulnerabile:
- dalla 18.7 prima della 19.1.8
- dalla 19.2 prima della 19.2.6
- dalla 19.3 prima della 19.3.2
GitLab.com e GitLab Dedicated non sono interessati. Lo stesso rilascio del 10 settembre ha corretto anche CVE-2026-87719 (CVSS 9.9), un bug di deserializzazione insicura raggiungibile da utenti autenticati con accesso a Duo Chat — un motivo in più per prendere l'intero rilascio, non solo la correzione della traversal.
Perché una lettura file arbitraria è una compromissione totale
"Lettura file" suona circoscritto. Su un server GitLab non lo è, perché i file leggibili dall'utente gitlab sono le chiavi dell'intera istanza e di tutto ciò che essa raggiunge. L'obiettivo più prezioso è secret_key_base — il segreto master di Rails, conservato in /etc/gitlab/gitlab-secrets.json sulle installazioni Omnibus (o in config/secrets.yml sulle installazioni da sorgente). Firma ogni cookie di sessione e ogni verifier firmato che GitLab emette, quindi recuperarlo trasforma una lettura file in account takeover e, da lì, in esecuzione di codice:
- Falsificare un cookie di sessione per qualsiasi utente, admin inclusi — accesso completo senza credenziali.
- Falsificare un token di registrazione runner, registrare un runner CI controllato dall'attaccante ed eseguire comandi arbitrari sull'infrastruttura di build.
- Falsificare i token di reset password per impossessarsi di account, 2FA compresa.
Ecco perché il vettore CVSS ha scope alterato: l'impatto esce da GitLab ed entra nei runner, nei cloud provider e nei registry che GitLab autentica. Il resto del bottino è la supply chain CI/CD in un secchio:
| Cosa espone la lettura | Dove vive | Cosa sblocca |
|---|---|---|
secret_key_base |
gitlab-secrets.json / secrets.yml |
Forgiatura sessioni + token → admin, poi RCE via runner malevolo |
.gitlab_shell_secret |
.../gitlab-shell/.gitlab_shell_secret |
Chiamate interne all'API falsificate verso il backend Git |
| Chiavi host SSH | /etc/gitlab/ssh/ |
Impersonificazione del server, MITM su Git-over-SSH |
| Variabili CI/CD | DB / config renderizzata | Chiavi cloud, credenziali registry, chiavi di firma delle pipeline |
| Deploy token e runner token | DB / config GitLab | Accesso persistente ai repo, push ai registry, runner abusivi |
| Credenziali del database | gitlab.rb / database.yml |
Lettura diretta del DB — ogni hash utente, token, progetto |
Letti secret_key_base e le variabili CI/CD, l'attaccante non ha più bisogno di toccare GitLab. Possiede la tua pipeline di build — il punto dove il codice diventa artefatto firmato e fidato che scorre in produzione e verso i clienti. È esattamente la forma di un incidente di supply chain software, innescato da un singolo POST non autenticato.
"Abbiamo applicato la patch entro la scadenza." "Quand'è che l'istanza era raggiungibile e senza patch?" "Il 10 e l'11, prima della finestra di change." "Allora dai per letti l'11 il
secret_key_base, ogni deploy token e ogni variabile CI/CD. La patch ha fermato la lettura. Non l'ha annullata."
La finestra di sfruttamento è già aperta
Metti in fila la cronologia e il problema è evidente. Patch rilasciata il 10 settembre. Disclosure pubblica e inserimento in KEV l'11 settembre. Sonde attive osservate dalle 06:00 UTC dell'11 settembre. Il divario tra una correzione che atterra e un exploit funzionante spruzzato sulle istanze esposte ormai si misura in ore, non in settimane — uno schema che quest'anno ha retto per ogni falla pre-auth ad alto valore, dalle VPN di edge alla CI/CD.
Per i difensori la conseguenza operativa è precisa: l'assenza di un log di autenticazione non è prova di sicurezza. Uno sfruttamento riuscito di CVE-2026-85706 non genera nessun login fallito, nessuna sessione, nessun evento di audit del tipo attorno a cui sono costruite le regole di correlazione del SIEM. Genera una richiesta HTTP con una forma riconoscibile. Se non stavi catturando e ispezionando URI e corpi delle richieste verso /api/v4/projects/*/repository/commits/, non hai alcuna traccia lato host di essere stato colpito — e l'attaccante, se prestava attenzione, ha letto anche la tua configurazione di logging.
Remediation
Trattalo come un incidente di esposizione credenziali, non come un ticket di patch. La correzione ferma le letture future; non fa nulla su ciò che è già stato preso.
1. Sono interessato? Verifica versione ed esposizione:
# Versione (self-managed)
sudo gitlab-rake gitlab:env:info | grep -i version
# oppure, in container:
cat /opt/gitlab/version-manifest.txt | head -1
# Qualsiasi progetto pubblico = precondizione sfruttabile
sudo gitlab-rails runner 'puts Project.where(visibility_level: 20).count'
Qualsiasi versione dalla 18.7 fino a (esclusa) 19.1.8 / 19.2.6 / 19.3.2 è vulnerabile. GitLab.com e Dedicated no.
2. Patch — versioni corrette esatte. Aggiorna a 19.1.8, 19.2.6 o 19.3.2 sul ramo di rilascio corrispondente (testuali dal security release di GitLab). Prendi l'intero rilascio per chiudere anche CVE-2026-87719.
3. Non puoi applicare la patch subito? Controlli compensativi. Sul reverse proxy o sul WAF, blocca o fai fallire le richieste verso /api/v4/projects/*/repository/commits il cui corpo contiene un parametro file.path con sequenze di traversal (../, %2e%2e, varianti codificate). È una patch virtuale, non una correzione: compra la finestra di change, nulla di più. Dove possibile, limita l'esposizione dell'API alle reti fidate.
4. Caccia alla compromissione. Cerca nei log di accesso e in eventuali corpi di richiesta catturati i POST all'API commits che portano file.path. Concretamente:
grep -E 'POST .*/api/v4/projects/[0-9]+/repository/commits' \
/var/log/gitlab/nginx/gitlab_access.log* | grep -i 'file%2epath\|file.path'
Mappa l'attività su MITRE ATT&CK: accesso iniziale con T1190 (Exploit Public-Facing Application), furto credenziali con T1552.001 (Unsecured Credentials: Credentials in Files) e — per eventuali sessioni falsificate a valle — T1078 (Valid Accounts). Qualsiasi hit — anche una singola richiesta che ha restituito un 200 con corpo di grandi dimensioni — significa dare per avvenuta la disclosure. IOC da prioritizzare: richieste da ASN cloud/VPS che non vedi di solito, raffiche di POST all'API commits su molti ID di progetto, e qualsiasi successiva attività autenticata con un token che non hai emesso tu.
5. Eradica + verifica. Poiché la lettura è silenziosa, per un'istanza esposta l'unica ipotesi sicura è la disclosure totale dei segreti. Dopo la patch:
- Ruota
secret_key_baseseguendo la procedura documentata di rotazione dei segreti di GitLab. Questo invalida ogni sessione falsificata e ogni token firmato — tutti gli utenti vengono disconnessi, ed è proprio quello che serve. - Ruota
.gitlab_shell_secret, le chiavi host SSH e la password del database. - Revoca e riemetti ogni deploy token, ogni token di registrazione runner e ogni variabile CI/CD — in particolare chiavi dei cloud provider, credenziali dei registry e chiavi di firma. Ruotale alla sorgente (AWS, GCP, il registry), non solo in GitLab.
- Ri-registra i runner con token nuovi; deregistra qualsiasi runner che non riconosci.
- Conferma la pulizia dopo la patch: ri-esegui la caccia nei log, verifica che non persistano token o runner sconosciuti, e fai il diff della configurazione CI/CD per step iniettati.
La rotazione è il passo che la maggior parte degli articoli salta, ed è quello che chiude davvero l'incidente. Un GitLab con la patch ma ancora con un secret_key_base letto l'11 settembre è un GitLab con la patch e compromesso.
Dove si inserisce Zero Hunt
La domanda difficile che questo bug impone non è "esiste una patch" — esisteva prima che iniziasse lo sfruttamento. È "la mia istanza specifica è davvero raggiungibile e sta perdendo dati, e la perdita si concatena fino all'RCE sulla mia configurazione?" A questa domanda si risponde attaccando la propria superficie, sull'orologio che sta usando l'attaccante.
Le campagne change-triggered di Zero Hunt esistono esattamente per questo momento: un nuovo CVE contro software sul tuo perimetro avvia una campagna completa entro l'ora, senza aspettare il test trimestrale pianificato. Lo swarm generativo a 10 agenti non scarica un proof-of-concept pubblico — nella finestra tra zero-day e patch spesso non esiste. Un LLM locale scrive un exploit per-target contro il tuo GitLab, conferma se l'API commits restituisce secrets.yml, e se il secret_key_base recuperato falsifica una sessione che arriva all'esecuzione di codice — la raggiungibilità che un CVSS 10 solo implica. Ogni skill usata dallo swarm è ri-testata nell'AI Gym contro corpora come Vulhub e il set black-box da 314 CVE prima di girare in produzione, e ogni azione è firmata ECDSA in una catena di custodia che puoi consegnare all'auditor che ti chiede cosa hai fatto il 14 settembre.
La lettura stessa, e l'uso delle credenziali che ne segue, sono anche una storia di rete — ed è qui che il motore di AI Traffic Analysis guadagna il suo posto in questo incidente. L'exploit non lascia log di autenticazione, ma la richiesta attraversa il filo, e così tutto ciò che l'attaccante fa con i deploy token e le chiavi CI/CD rubati: i push al tuo registry, il runner abusivo che fa check-in, l'egress verso un ASN mai visto. Un modello di deep learning con quattro teste di inferenza sulla GPU dell'appliance legge quel comportamento mentre accade — non nel digest SIEM del mattino dopo che, per una lettura file non autenticata, non avrebbe nulla da mostrare. On-premise, senza callback verso il cloud: i segreti del tuo GitLab sono l'ultima cosa che vuoi far transitare dal modello di qualcun altro per scoprire che sono stati rubati.