WarmUp 题学习笔记:一次 PHP 代码审计的记录
记录一下我做 HCTF 2018 WarmUp 这道题时的理解过程,主要是搞懂几个一开始卡住我的问题。
题目背景
打开靶机只有一个滑稽图。

查看源码发现注释提示 source.php。访问后拿到源码,访问 hint.php 得到提示:flag 在 ffffllllaaaagggg 文件里。
核心代码(简化):
class emmm {
public static function checkFile(&$page) {
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
// ... 若干检查 ...
// 每次检查通过就 return true,全部失败 return false
}
}
if (!empty($_REQUEST['file']) && is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])) {
include $_REQUEST['file']; // ← 真正读文件的地方
} else {
echo "<img src=滑稽图/>"; // ← 检查不过就显示图片
}
我的几个疑问与解答
疑问 1:checkFile 只是返回 true,flag 是怎么拿到的?
一开始我卡在这里:函数只是返回一个 true,那 flag 到底怎么出来?
搞懂后发现是三者配合,缺一不可:
- checkFile 的 true:唯一作用是"骗过安检",让程序走到
include那一行(否则只显示滑稽图)。它自己不读任何东西。 - include:才是真正"读文件"的动作。它把参数当文件路径,读出内容显示。
../:不是被执行的代码,是文件路径里的"上一级目录"记号,操作系统靠它一层层往上找文件。
传入: ?file=hint.php?/../../../../ffffllllaaaagggg
↓
checkFile 用 "?" 截取出 "hint.php" → 在白名单 → 返回 true(拿到门票)
↓
include 拿到的却是完整原路径 hint.php?/../../../../ffffllllaaaagggg
↓
操作系统解析 "../" 一层层往上 → 定位到根目录的 ffffllllaaaagggg → 输出 flag
关键漏洞点:checkFile 检查的是截取后的值(hint.php),而 include 用的是截取前的原值。而且函数内部只把结果赋给 $_page,从没改过 $page,所以外面的 $_REQUEST['file'] 一直保持原样。
疑问 2:../ 是怎么"执行"的?
../ 从不被"执行"。它是路径的一部分,给操作系统看的导航:
include "hint.php?/../../../../ffffllllaaaagggg"
起点 /var/www/html
hint.php? → 进入这个"目录"
../ → 退回 html
../ → www
../ → var
../ → 根目录 /
ffffllllaaaagggg → 目标文件!
说明:
hint.php?目录并不存在,但操作系统会把某目录/../成对抵消掉(路径归一化),所以不用等它真实存在。这也解释了为什么要 4 个../:1 个抵消hint.php?/,3 个从 html 爬到根目录。
疑问 3:为什么 payload 前面要加 file=?
因为代码用 $_REQUEST['file'] 取值。file= 是"收件人名字",必须和代码里的 'file' 对上:
?file=hint.php?/../.. → $_REQUEST['file'] = "hint.php?/../.."
↑名字 代码里的 ['file'] 取这个名字
名字写错(如写成 page=),PHP 就放进别的抽屉,代码取不到,等于白传。
疑问 4:file 前面那个 ? 是因为 checkFile 检查两次吗?
不是。 payload 里其实有两个 ?,作用完全不同:
http://目标/source.php ? file = hint.php ? /../../../../ffffllllaaaagggg
↑ ↑
①号 ? ②号 ?
- ①号
?:URL 语法的固定标点,分隔"路径"和"参数",所有网址都这样写,和 checkFile 无关。去掉它服务器就不认file=...是参数。 - ②号
?:漏洞利用自己构造的,给 checkFile 当截断点,截出hint.php骗白名单。这个才和检查有关。
疑问 5:为什么我加的 %23(#)不对?
我曾经写 hint.php?%23/../../../,想用 # 当分隔符。错在两点:
#在 URL 里是锚点标记,浏览器会把#后面的内容截掉不发给服务器。服务器只收到hint.php?,../全丢,include 找不到文件。- 根本不需要
#:?本身就是分隔符,后面直接接/../就行。
正确写法:hint.php?/../../../../ffffllllaaaagggg
完整 Payload 与结果
http://<靶机>/source.php?file=hint.php?/../../../../ffffllllaaaagggg

CTF2{6cf95f90-479b-4844-83dc-f388f6e9c43a}
../层数看靶机目录深度,本环境 4 层刚好,实际可多试几层。
一句话总结
用
?骗 checkFile 说"我要访问 hint.php"(拿到 true 的门票),
但 include 真正读的还是那串带../的完整路径,
操作系统顺着../一层层往上爬,把根目录的 flag 读了出来。
学习记录,欢迎指正。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)