XYCTF2025web个人复盘

这次参与XYCTF,虽然没有怎么参与(只认真打了一天),但是可以看出里面是有很多东西值得学习的,所以还是记录并复现一下吧。
Web
题解
Signin
这道题是我唯一做出来的web题,虽然总结来看考点还是比较简单而清晰的,但我还是做了很久。()
首先分析源码:
1 | # -*- encoding: utf-8 -*- |
可以得知这里有secret.txt
,于是我们用目录穿越去读取它,得到secret的值。
由于对目录穿越的过滤很简单,所以可以轻松使用./.././../
的形式绕过。
然后就是分析bottle框架的session生成机制了。
由于我们获得了secret
的值,所以可以直接生成想要的合法cookie,于是接下来就要看bottle是如何解析cookie的了:
1 | def get_cookie(self, key, default=None, secret=None, digestmod=hashlib.sha256): |
可以看到,里面用到了pickle.loads()
,所以这样就可以利用pickle反序列化漏洞了。(其实这里分析了好久来着,完全没看出来呜呜)
写脚本生成payload
:
1 | import os |
然后读flag即可。
复现
Fate
这道题其实我没怎么看,因为看到SSRF的过滤,然后还看到sql,就觉得完全没有思路,于是放弃了。
看过题解之后,其实貌似并不那么难,但确实也都是我没涉及过的知识。
1 | cur.execute(f"SELECT name FROM country WHERE code=UPPER('{code}')") |
这里的sql注入要利用到flask.request.get_json()
,这个方法没有对传入的类型做检查,所以我们可以传入非字符串类型的变量。
1 python -c "a = [1]; print(f'test{a}')"
这时,程序会输出test[1]
,这便是可以利用的点。
所以当传入的code
参数为["1') UNION SELECT FLAG FROM FLAG --","1"]
这样的数组时,就会成功将执行代码修改为:
1 | SELECT name FROM country WHERE code=UPPER('["1') UNION SELECT FLAG FROM FLAG --","1"]') |
然后要解决的就是SSRF部分的问题:
1.在前面加入lamentxu.top,这个可以用@来绕过。
2.禁止了所有字母和.
,那么我们使用2130706433来表示127.0.0.1。
3.必须要传入参数0为abcdef。使用二次URL编码绕过。
然后就可以进行注入了。
Now you see me 1
拿到附件之后首先要做的是找到程序源码,给出的代码中可以看到中间藏了一段代码,复制出来拿到真正的源码:
1 | # YOU FOUND ME ;) |
其实可以很简单地看出是SSTI,但我在本地起了一个服务测试之后发现fenjing跑不出来,遂放弃。(或许需要找个机会恶补一下了,不能再当脚本小子了呜呜)
按照LAMENTXU大佬的官方题解:
先考虑传统继承链。但是由于缺少
_
,只能去尝试构造字符_
,但是由于限制了单双引号和一些重要字符,无法获取到_
。传统继承链打不了。没有过滤request对象,但是可以发现request的常用逃逸参数(args,values这种)全被禁止。同时限死了单双引号,无法拼接,无法进行编码转换。
可以使用
request.endpoint
获取到当前路由的函数名,即r3al_ins1de_th0ught
从中,我们能获取字符’d’, ‘a’, ‘t’
注意到可以拼接出data。进而获取
request.data
,再在请求体中传入任意字符进行绕过。至此,我们可以获得任意字符。
可以知道需要根据特定的过滤来考虑不同的绕过与字符获取方式,仅靠自动化工具是无法解决所有问题的。
然后是reload被删除的方法:
1 | import os |
最后需要注意的是要在语句的开头加入#}
来闭合注释语句。
来自大佬的总结:总结如下:
1.
#}
闭合注释语句
2.request.endpoint找request.data
3.request.data从请求体中获取任意字符
4.通过拼接字符打继承链找到importlib的reload。分别reloados.popen
和subprocess.Popen
5.通过request打继承链找os打RCE
总觉得完完全全在抄大佬的博客呢(╥ω╥)
ezsql(手动滑稽)
这里看别人的博客没有太过详细的介绍,于是只能根据步骤一步步复现了
第一步是一个简单的万能密码登录,但是有过滤。
并不是很清楚具体过滤了什么,但是测试下来好像空格和符号都被过滤了。(不确定)
使用题解的payload:username=admin'%09OR%091=1#&password=1
,用%09绕过空格过滤。
进入到后台,发现需要密钥
分析一下题解的exp:
1 | import requests |
解码后的Payload:
1 | username=admin' OR substring( |
将获取到的特定位置的字符与字符集对比,如果正确返回,则为成功,通过这种方式就可以得到secret:dtfrtkcc0czkoua9s
但是在此之前,我们需要通过相同的方式首先得到库名、表名和列名:
1 | sql = f"username=admin'%09OR%09substring(database()%09FROM%09{pos}%09FOR%091)='{char}'%23&password=1" |
得到管理员密钥之后登录进去,就有一个简单的无回显命令执行,发现有空格过滤,直接使用$IFS绕过即可,再将回显重定向到其他文件,即可读到flag。
payload:head$IFS/flag.txt$IFS>1.txt
ez_puzzle
本来以为是简单的前端题,结果发现js有点抽象,于是没写出来,按照官方题解的思路来一遍吧:
禁用F12和右键,所以在更多工具中找到开发者工具:

发现有debugger
,直接下断点过掉:

然后可以开始分析了。
原本我的思路是分析获胜判断条件,但发现混淆太复杂,连ai都没分析出来,于是就放弃了。
但是这题既然与时间有关——限定两秒内,所以就可以从时间下手,全局搜索time
,能够找到starttime
和endtime
,设定starttime
为一个很大的数,就可以实现跳过时间判断:

再拼好拼图就可以了:

- 标题: XYCTF2025web个人复盘
- 作者: Wang1r
- 创建于 : 2025-04-07 20:15:39
- 更新于 : 2025-04-08 14:02:31
- 链接: https://wang1rrr.github.io/2025/04/07/XYCTF2025web个人复盘/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。