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);
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 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);
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. :)');
}
});
前言
nodejs感觉有好多东西要学,看的比较杂,还是系统的做一做题总结一下吧,以后有机会再去深入
web334
题目给了个zip,解压下来就是源码
login.js
user.js
登录题目是个登录框,明显要登录进去,分析下代码后注意看这里
发现传入的不能为CTFSHOW,但它会把它进行大写的转化,转化后要等于CTFSHOW,密码也就是123456,很简单,直接构造
不过之前看原型链污染的时候看到了toUpperCase()和toLowerCase()这两个函数,这里就顺便总结一下
他们本来的功能是大小写转换,但是由于他们混入了两个特殊的字符也可以转化
P神文章里详细fuzz了toUpperCase这个函数:https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
web335
打开f12里面源码找到提示
随便传了个值被返回了出来
猜测应该是代码执行,后端代码应该是eval('console.log(xxx)'),网上搜一下用
child_process
模块去打。(如果用exec函数,返回值还是一个ChildProcess,所以要用execsync或者soawnsync)官方文档继续找
web336
和上面一道题一样,不过测试发现过滤了exec,用第二方法就好了,也可以用空格进行拼接
?eval=require('child_process')'ex'+'ecSync'(要url编码一下,+号会被url解析为空格)
web337
给了源码
直接看这里
php的弱类型做多了自然想到了数组,先试试/?a[]=1&b[]=2,发现不行。然后再仔细读了读代码,首先a[]=1&b[]=2这样传后相当于传的是a[0]=1&b[0]=2,也相当于定义一个变量a=[1]&b=[2]再进行拼接得到的是1flag和2flag,他们两个的md5明显不相同,那怎么办呢。
由于js里面传数组只能是数组索引,当传入非数字索引的时候?a[x]=1&b[x]=2。a和b就变成了里面的对象了
这样拼接后的md5就相同了,也就可以绕过了
看了feng师傅的文章也学到了第二种方法
当传入a[]=1&b[]=2的时候,req.query.a返回的是a=[1]&b=[2]。也是数组,把数组进行拼接的时候是这样的
那就不执着于md5那里了,直接构造?a[]=1&b=1就好了
web338
给了源码,主要是login.js这一部分
主要是这里
去看一看copy函数
先看看 JavaScript Prototype 原型链污染
可以类比p神文章里面的merge,这个形式也是比较明显的原型链污染了,用payload抓包后直接打就好了
简单点说就是req.body使我们传进去的,而我们通过这个方法去污染user的原型链,也就污染了object,基类里面也就会存在ctfshow:36dboy了。
web339
其实在做这道题之前,强烈建议把p神那篇文章弄透,放两个连接:
https://xz.aliyun.com/t/7184#toc-2
p神
拿到后和上面一道题是差不多的,不过在route里面多了一个api.js
api.js
然后login.js里面登录成功的条件换成了
本来只需要满足污染后有个ctfshow等于flag就可以了,但其实做起来才发现,flag的值根本找不到,换句话说,这道题的突破口可能根本不在这。
在回过头来看看api.js,这个模板起的是渲染的作用
这里面也有两个点值得注意,首先是使用了
express
框架,express框架可以通过设置Content-Type来进行JSON解析,还有一个点是{query:Function(query)(query)}
,这个点和p神文章里面的拼接是相似的。也就是说,污染后构造的条件不再是满足login的条件,而是让渲染api模板的时候构造一个恶意的function,也就是控制里面的query。
先在登录的时候抓包污染一次
然后再抓包post访问/api,我之前想了一个特别弱智的问题,为啥不能url直接传参/api进入,其实抓包的时候进入login别人就是post进入的,所以直接改路由就好了。
进入后相当于触发了那个方法,监听成功了找flag就行,falg在/app/routes/login.js里面
解释一下p神文章里面为什么没有用require函数
然后看师傅们的wp学习发现还有一种方法
大概就是利用ejs这个模板渲染引擎,具体也没去太了解(主要自己太菜了
参考文章:
https://xz.aliyun.com/t/6113
Express+lodash+ejs: 从原型链污染到RCE
然后直接用payload打就行了
发完包后只要找到一个有render渲染的页面(首页就行)刷新一下就好了
web340
基本和上一题差不多,主要是login.js里面的这一段变了
调试一下,看到确实是第二层的prototype才是object,意思就是套两层就可以了,其他步骤和上面一样
不过其实我觉得可以通过污染两层方法里面的变量让if满足后输出flag,可是我不会构造。。。还是太菜
web341
没有了api.js,用之前ejs漏洞,只需要找到一个有render的界面就可以了,传入payload后刷新一次就好
web342/343
参考链接https://xz.aliyun.com/t/7025
模板引擎被改成了jade
改成POST发包到/login,再加上Content-Type: application/json,传入payload
再刷新一下页面就监听成功了
web344
直接给了源码
req.query.query,明显是让我们传参一个数组为/?query={"name":"admin","password":"ctfshow","isVIP":true}
但是注意他是过滤了8c|2c,8c网上查了是一个特别奇怪的字符,不用管,2c是
,
的url编码,因为req.url是经过url编码的,也就是说他会解码一次,不过这里编码逗号绕不过,因为2c被过滤了。看了feng师傅的博客,发现这里要用到node.js本身的特性。传入:?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}
最后
连着搞了3天,感觉好多东西学的很浅显,也没有深入去学习,这个真的好难啃啊,等以后代码功底好一点了,再来接着搞。