F12有源码
const express = require('express');
const path = require('path');
const opn = require('opn');
const crypto = require('crypto');
const session = require("express-session");
const bodyParser = require('body-parser');
const stringRandom = require('string-random');
const app = express();
const FUNCFLAG = '_$$ND_FUNC$$_';
const serialize_banner = '{"banner":"Congratulations! 你是目前最高分!"}';
app.use(bodyParser());
app.use(bodyParser.json());
const logs={};
var highestScore = 400;
const serialize = function(obj) {
var outputObj = {};
var key;
if (typeof obj === 'string') {
return JSON.stringify(obj);
}
for(key in obj) {
if(obj.hasOwnProperty(key)) {
if(typeof obj[key] === 'function') {
var funcStr = obj[key].toString();
outputObj[key] = FUNCFLAG + funcStr;
} else {
outputObj[key] = obj[key];
}
}
}
return JSON.stringify(outputObj);
};
const validCode = function (func_code){
let validInput = /process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|\[|\+|\*/ig;
return !validInput.test(func_code);
};
const validInput = function (input){
// filter bad input
let validInput = /process|child_process|main|require|exec|this|function/ig;
ins = serialize(input);
return !validInput.test(ins);
};
// not safe
const unserialize = function(obj) {
obj = JSON.parse(obj);
if (typeof obj === 'string') {
return obj;
}
var key;
for(key in obj) {
if(typeof obj[key] === 'string') {
if(obj[key].indexOf(FUNCFLAG) === 0) {
var func_code=obj[key].substring(FUNCFLAG.length);
if (validCode(func_code)){
var d = '(' + func_code + ')';
obj[key] = eval(d);
}
}
}
}
return obj;
};
const merge = function(target, source) {
try{
for (let key in source) {
if (typeof source[key] == 'object' ) {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
catch (e) {
console.log(e);
}
};
const genSanbox = function (req){
var content = stringRandom(32);
var result = crypto.createHash('md5').update(content).digest("hex");
req.session.sanbox = result;
logs[result] = new Record();
return result;
};
const getSanbox = function (req){
return req.session.sanbox;
};
app.use(session({
secret: 'hahahahahaha@@@@@@',
name : 'sessionId',
resave: false,
saveUninitialized: false
}));
app.use(function(req, res, next){
if(!getSanbox(req)){
genSanbox(req);
}
if(validInput(req.body)) {
next();
} else{
res.status(403).send('Hacker!!!');
}
});
const clearEnvir = function (){
for(key in Object()){delete {}.__proto__[key]};
};
function Record(){
this.lastScore=0;
this.maxScore=0;
this.lastTime=null;
}
async function record (req, res, next){
new Promise(function (resolve, reject) {
var sanbox = getSanbox(req);
var record = new Record();
var score = req.body.score;
var oldRecord = logs[sanbox];
console.log(score);
clearEnvir();
if (score.length<5){
merge(record, {
lastScore: score,
maxScore: parseInt(logs[sanbox].maxScore)>parseInt(score)?logs[sanbox].maxScore:score,
lastTime: new Date().toString()
});
logs[sanbox] = record;
oldRecord.maxScore = record.maxScore;
highestScore = highestScore > parseInt(score)? highestScore: parseInt(score);
if((score - highestScore)<0){
var banner = "再接再厉,马上就要赶上最高分了!";
}else{
// var serialize_banner = req.params.data;
var banner = unserialize(serialize_banner).banner;
}
}else{
banner="我都打不了这么高, 你小子肯定作弊了";
}
clearEnvir();
res.json({
banner: banner,
record: oldRecord
});
}).catch(function(err){
next(err)
})
}
app.post('/record', record);
app.get('/token', function(req, res){
token = getSanbox(req);
res.end(token);
});
代码很长。大致就是。
有一个unserialize操作。里面有eval执行函数。
但是会经过两个check。
const validCode = function (func_code){
let validInput = /process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|\[|\+|\*/ig;
return !validInput.test(func_code);
};
const validInput = function (input){
// filter bad input
let validInput = /process|child_process|main|require|exec|this|function/ig;
ins = serialize(input);
return !validInput.test(ins);
};
并且存在merge。
大致方向就是一个原型参数污染+反序列化RCE
首先。我们看可控点
async function record (req, res, next){
new Promise(function (resolve, reject) {
var sanbox = getSanbox(req);
var record = new Record();
var score = req.body.score;
#这里。接受了我们传递的score
var oldRecord = logs[sanbox];
console.log(score);
clearEnvir();
#这里score的length必须小于5
if (score.length<5){
merge(record, {
lastScore: score,
maxScore: parseInt(logs[sanbox].maxScore)>parseInt(score)?logs[sanbox].maxScore:score,
lastTime: new Date().toString()
});
#进行了merge操作。可进行原型链污染
logs[sanbox] = record;
oldRecord.maxScore = record.maxScore;
highestScore = highestScore > parseInt(score)? highestScore: parseInt(score);
if((score - highestScore)<0){
var banner = "再接再厉,马上就要赶上最高分了!";
}else{
//进入unserialize。下一步利用反序列化RCE
var banner = unserialize(serialize_banner).banner;
}
}else{
banner="我都打不了这么高, 你小子肯定作弊了";
}
clearEnvir();
res.json({
banner: banner,
record: oldRecord
});
}).catch(function(err){
next(err)
})
}
首先。进行参数污染
这边
function Record(){
this.lastScore=0;
this.maxScore=0;
this.lastTime=null;
}
lastScore已经被设置为了0。一个__proto__。得到number
第二个__proto__得到function
然后就可以执行任意代码了
但是我们还得绕过
https://xz.aliyun.com/t/7184#toc-8
这是反序列RCE的payload。但是。我们发现。这里的function被过滤了。
那么我们可以通过``.constructor.constructor得到object对象。然后执行
由于。字符都被过滤。我们可以通过16进制绕
自行忽略背景~~~~
将我们的代码16进制一下。然后执行
现在就可以任意代码执行了。用文件读取flag。然后和字符串比较。如果比较正确。就继续。不正确就直接报错。作为一个盲注使用