RCTF-2025
本文最后更新于 2025年11月19日 晚上
这次比赛没有 Android 题 qwq,还想做做 Android 题的(虽然我不会)(,已严肃学习

Web
photographer
审计 /index.php 代码,这里完成了加载自动加载器 autoload.php,鉴权初始化以及路由分发,请求经过 Apache 的 .htaccess 重写进入该入口,随后由路由器将 URL 映射到控制器方法
1 | |
鉴权初始化关键地方在 Auth::init(),从会话中读取当前用户 ID 查询用户对象,这里的查询把 user 和 photo 表进行了连接,使用了 SELECT * 查询
1 | |
这里把用户类型值与 admin 的值进行比较,只有已登录且用户权限等级低于 admin 才能看到 flag
1 | |
可以发现 admin=0,auditor=1,user=2
1 | |
这里的 User::findById 将用户表与图片表通过 LEFT JOIN 连接,并且使用 SELECT *,把两张表的所有列合并成一个关联数组,如果存在同名列,右表会覆盖左表。user 表有 type(用户角色),photo 表也有 type(图片 MIME 类型)。所以只要设置了背景图(user.background_photo_id 指向某张 photo 记录)之后,查询结果中的 type 字段就来自 photo.type 而不是 user.type,相当于 user.type 被 photo.type 覆盖了
1 | |
而 Auth::type() 实际上读取的是图片的 type 字段。如果让某张图片的 type 变成小于 0 的值,就能拿到 flag ,图片的 type 是从上传接口写入的,这个接口直接把 $_FILES['type'] 原样存进数据库,而 $_FILES['type'] 由请求中的 multipart/form-data 文件分段头的 Content-Type 决定,完全可由客户端控制
1 | |
图片校验函数 isValidImage 并不校验 Content-Type,它仅验证扩展名、大小以及 getimagesize 是否能读出基本信息。因此我们既可以上传一张合法的 PNG/JPG 文件来满足这些检查,又可以在分段头将 Content-Type 写成任意字符串
1 | |
因此,将文件分段头设为 Content-Type: -1 上传一张满足扩展名和 getimagesize 的合法图片,得到这张图片的 photo_id。然后调用 /api/user/background 将其设为当前用户的背景图。此时再访问 superadmin.php,由于 Auth::type() 读取的是 photo.type 且它是 -1,满足 < admin(0)
1 | |

1 | |
auth
这道题是通过注册漏洞+断签名攻击,将用户的 SAMLResponse 包装成管理员身份
身份提供方 idp: http://auth.rctf.rois.team/
服务提供方 sp: http://auth-flag.rctf.rois.team:26000/
1 | |
审计代码发现只在 parseInt(type)==0 时才检查邀请码,用 type=0x10 可以绕过进行注册,并且 samlController.idpInitiatedSSO/sso 还要求 req.session.userType == 0,虽然绕过了注册但用户实际存储的 type 不为 0
1 | |
SP 的 /admin 接口会检查会话中的 email 是否等于 admin@rois.team
1 | |
触发 IDP 发回的 SAMLResponse,串联两份 Assertion 并把其中一个的 NameID 改成 admin 后再提交给 SP
参考战队师傅的脚本
1 | |
拿到 flag
1 | |
Misc
Signin
把 score 改大

1 | |
Shadows of Asgard
Challenge 1: The Merchant’s Mask
1 | |
看 http 流量包

1 | |
Challenge 2: The Parasite’s Nest
1 | |
查看带有 png 的流量包,有 aeskey 和 iv 和 data

aes 直接解密
1 | |
解出来一些信息
1 | |
路径
1 | |
Challenge 3: The Hidden Rune
1 | |
一个一个提取流量包里的 PNG 数据带入上面的脚本 data 中解密,加密字段如下
1 | |
解密为
1 | |
taskid
1 | |
Challenge 4: The Forge of Time
1 | |
密文
1 | |
解密
1 | |
答案
1 | |
Challenge 5: Raven’s Ominous Gift
1 | |
有一段数据解出来发现保存了 flllllag.txt 文件
1 | |
对 png 内密文解密
1 | |
解密为
1 | |
base 解码
1 | |
最终 flag
1 | |
Speak Softly Love
Challenge 1: Video ID
1 | |
截图谷歌识图搜索,youtube 视频就是

https://www.youtube.com/watch?v=8ssDGBTssUI,ID 为 v 的值
1 | |
Challenge 2: Code Revision
1 | |
要提交 Code revision 代码修订号,根据视频下的信息找到 svn://svn.mateusz.fr/dosmid
从 SVN 仓库检出源码查看修订记录
1 | |
这里的 soft errors

1 | |
Challenge 3: Name-pronunciation URL
1 | |
点 here it is

1 | |
Challenge 4: Donation address
1 | |
查找捐赠地址,这里有一个 gopherspace node,访问

1 | |

找到地址

1 | |
拿到 flag
1 | |
Wanna Feel Love
Challenge 1
1 | |
给了一个邮箱文件,outlook 打开可以发现还有一个 challenge.xm 文件

一个垃圾邮件的隐写,https://www.spammimic.com/decode.cgi,这个网站可以解密
1 | |
Challenge 2
1 | |
需要给出 xm 里面藏有的信息,用 OpenMPT 打开在 Samples5 的 Feel 波可以看到像条形码一样的

二进制转化,黑色 0 红色 1
1 | |
解密出来为
1 | |
Challenge 3
1 | |
搜索字符串能找到这种东西,有点哈人

找到信息链接
1 | |

1 | |
Challenge 4
1 | |
找购买她 DVD 的链接,询问 AI 可以找到

gpt 有点傻,sender 分析错了,但在给的链接里面找到了,https://yitzilitt.medium.com/the-story-behind-i-feel-fantastic-tara-the-singing-android-and-john-bergeron-fc83de9e8f36

1 | |
Challenge 5
1 | |
找数字墓地,评论区有个网页

跳转到
1 | |
最终 flag
1 | |
Asgard Fallen Down
Challenge 1: The First Command
1 | |
和前面的流量包加密方式相同,AES 加密,key 和 iv 在 html 中

在请求包数据中密文,两次 base64 解码
1 | |

1 | |
Challenge 2: The Heartbeat
1 | |
查找每次攻击的间隔,AI 一把梭了

1 | |
Challenge 3: The Heart of Iron
1 | |
找机器 CPU,解密密文

1 | |
Challenge 4: Odin’s Eye
1 | |
找截图使用的工具,从 2786 数据流在对数据进行分片传输,一共 15 片

导出数据进行拼接
1 | |

1 | |
flag
1 | |
Reverse
chaos
点击就送
1 | |
chaos2
这里有花指令,全部 nop 掉重新识别函数

1 | |
RC4 加密,dword_404018 是 RC4 的密钥,但是其实是错误的
1 | |
往上翻可以发现这里有几处代码在对密钥进行修改
1 | |
可以反推正确密钥是 flag:{ThisflagIsGoods},并且有 0x80 个密钥可以用 0 补全所以脚本如下
1 | |
解得 flag
1 | |