[GYCTF2020]Ez_Express刷题笔记
index.js
var express = require('express'); var router = express.Router(); const isObject = obj => obj && obj.constructor && obj.constructor === Object; const merge = (a, b) => { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a) => { return merge({}, a); } function safeKeyword(keyword) { if(keyword.match(/(admin)/is)) { return keyword } return undefined } router.get('/', function (req, res) { if(!req.session.user){ res.redirect('/login'); } res.outputFunctionName=undefined; res.render('index',data={'user':req.session.user.user}); }); router.get('/login', function (req, res) { res.render('login'); }); router.post('/login', function (req, res) { if(req.body.Submit=="register"){ if(safeKeyword(req.body.userid)){ res.end("<script>alert('forbid word');history.go(-1);</script>") } req.session.user={ 'user':req.body.userid.toUpperCase(), 'passwd': req.body.pwd, 'isLogin':false } res.redirect('/'); } else if(req.body.Submit=="login"){ if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")} if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){ req.session.user.isLogin=true; } else{ res.end("<script>alert('error passwd');history.go(-1);</script>") } } res.redirect('/'); ; }); router.post('/action', function (req, res) { if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")} req.session.user.data = clone(req.body); res.end("<script>alert('success');history.go(-1);</script>"); }); router.get('/info', function (req, res) { res.render('index',data={'user':res.outputFunctionName}); }) module.exports = router;
其中下段代码存在原型链污染
const merge = (a, b) => { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a) => { return merge({}, a); }
定位到clone函数的使用
router.post('/action', function (req, res) { if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")} req.session.user.data = clone(req.body); res.end("<script>alert('success');history.go(-1);</script>"); });
而这里的前提是以ADMIN账户登录
定位注册段代码
if(req.body.Submit=="register"){ if(safeKeyword(req.body.userid)){ res.end("<script>alert('forbid word');history.go(-1);</script>") } req.session.user={ 'user':req.body.userid.toUpperCase(), 'passwd': req.body.pwd, 'isLogin':false } res.redirect('/'); }
防护函数
function safeKeyword(keyword) { if(keyword.match(/(admin)/is)) { return keyword } return undefined }
这里user是经过toUpperCase()转换过的,此时可以掺入一些其他的字符,通过safeKeyword判断而转换后成为ADMIN
ı
字符经过toUpperCase()后为i
这里注册用户名为admı
n即可,登陆后即可通过对admin身份的判断
下一步考虑怎么利用原型链污染来拿到flag
info路由代码
router.get('/info', function (req, res) { res.render('index',data={'user':res.outputFunctionName}); })
res.outputFunctionName是undefined,这里可以通过原型链污染改变它的值,渲染到info路由
渲染过程中存在代码注入,原理和payload见 https://evi0s.com/2019/08/30/expresslodashejs-%e4%bb%8e%e5%8e%9f%e5%9e%8b%e9%93%be%e6%b1%a1%e6%9f%93%e5%88%b0rce/
再次访问info路由拿到flag