
Удаленное выполнение кода в DbGate через инъекцию functionName в конечной точке loadReader — CVSS 8.8
functionNameУровень серьёзности: высокий (CVSS 8.8)
CWE: CWE-94 — Некорректный контроль генерации кода («инъекция кода»)
Затронуто: dbgate-api ≤ 7.1.8 (исправлено в 7.1.9)
Рекомендация: GHSA-hv83-ggc4-v385
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-48017
Автор находки: Romain Deperne
Конечная точка POST /runners/load-reader принимает параметр functionName и подставляет его
напрямую в строковый шаблон JavaScript, который затем выполняется в форкнутом процессе — без
какой-либо очистки или проверки. Любой аутентифицированный пользователь (без специальных разрешений) может выйти за
пределы предполагаемого вызова и выполнить произвольный JavaScript, добившись удалённого выполнения кода на сервере.
Форкнутый runner устанавливает require = null в качестве песочницы, но process.binding("spawn_sync") остаётся доступным:
process.binding("spawn_sync") по-прежнему доступен и порождает реальный процесс ОС.
Я проводил аудит подсистемы runner в DbGate на предмет разрыва между защищёнными и незащищёнными путями
выполнения кода. Runner start() в runners.js:292 должным образом защищён — он вызывает
testStandardPermission('run-shell-script') и проверяет platformInfo.allowShellScripting.
loadReader() делает концептуально похожую вещь (собирает и запускает скрипт-загрузчик JS), но не имеет
ни одной из этих проверок. Я проследил поток данных:
runners.js:353 loadReader({ functionName, props })
runners.js:366 loaderScriptTemplate(prefix, functionName, ...)
runners.js:64 `... ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});`
packageTools.ts:33 return `dbgateApi.${functionName}` // ← no sanitization
functionName контролируется атакующим и попадает внутрь исполняемой строки шаблона. Префикс dbgateApi.
— единственное, что стоит перед ним, и его можно обойти: закрыв выражение с помощью toString();,
внедрив полезную нагрузку и закомментировав завершающий (${props}) с помощью //, получаем валидный JS.
Файл: packages/api/src/controllers/runners.js (loadReader → loaderScriptTemplate)
Файл: packages/tools/src/packageTools.ts:33 (compileShellApiFunctionName)
// packageTools.ts:33 — functionName flows in unsanitized
return `dbgateApi.${functionName}`;
// runners.js:64 — interpolated into the executed loader template
`const reader = await ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});`
Сгенерированный (внедрённый) скрипт:
const reader = await dbgateApi.toString();
process.binding("spawn_sync").spawn({ file:"/bin/sh", args:["/bin/sh","-c","id"], ... });
dbgateApi.toString//({});
compileShellApiFunctionName() был написан для преобразования короткого имени в полный путь API
(dbgateApi.<name>), неявно доверяя тому, что functionName является идентификатором. Однако это значение
поступает напрямую из тела HTTP-запроса. Поскольку оно конкатенируется в исходный код, который позже
выполняется через eval/fork, допущение о доверии приводит к RCE. Песочница с require = null не помогает —
внутренний process.binding("spawn_sync") Node обходит её.
Исправление (7.1.9): проверять functionName по строгому списку разрешённых идентификаторов перед компиляцией
в скрипт загрузчика.
poc/rce_loadreader_functionname_injection.py — сквозная проверка реальной конечной точки.
# with an existing JWT
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 <JWT> 'id > /tmp/pwned'
# or log in first
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 --login admin password 'id'
Скрипт формирует полезную нагрузку functionName, отправляет её на POST /runners/load-reader, и внедрённый вызов spawn_sync выполняет команду в форкнутом процессе runner.
Аутентифицированное RCE на хосте API DbGate. DbGate — это GUI для управления базами данных, часто развёртываемый с широким сетевым доступом к внутренним базам данных — выполнение кода на нём является мощным плацдармом для проникновения в уровень данных.
Раскрыто ответственно. PoC опубликован после выпуска исправления, для защитников и инженерии обнаружения.