[HCTF 2018]WarmUp(文件包含绕过)
题目描述
进入题目是一个网页,查看页面源码的注释中发现 source.php,访问后得到完整的 PHP 源代码:
1 |
|
代码分析
白名单中只允许两个文件:source.php 和 hint.php。
checkFile 函数对传入的 file 参数做了三层检查:
| 检查 | 处理方式 | 绕过思路 |
|---|---|---|
| 检查1 | 直接 in_array($page, $whitelist) |
必须精确匹配白名单 |
| 检查2 | 在第一个 ? 处截断,再匹配白名单 |
可以用 source.php?xxx 绕过,但include时 ? 后面的内容会被当作查询字符串丢弃 |
| 检查3 | 先 urldecode,再在 ? 处截断匹配 |
利用双重URL编码,使解码后截断匹配白名单,但原始值指向目标文件 |
如果三个检查都通过不了,返回 false。如果任意一个通过,就会 include $_REQUEST['file']。
关键点:
- 检查2 可以用
source.php?/../../flag绕过,但PHP的include在遇到?时会丢弃后面的内容,所以只能包含白名单文件本身 - 检查3 多了一步
urldecode,我们需要利用这个”额外解码”来做文章
理解两次URL解码
整个利用的核心在于 PHP 对参数进行了两次 URL 解码,且检查与 include 用的是不同的解码结果:
| 阶段 | 发生在哪 | 结果 | 用于 |
|---|---|---|---|
| 第①次解码 | PHP解析 $_GET/$_REQUEST 时自动完成 |
%25 → %,%253F → %3F(字面字符) |
include $_REQUEST['file'] |
| 第②次解码 | checkFile 中手动调用 urldecode($page) |
%3F → ?(真正的问号) |
在 ? 处截断匹配白名单 |
漏洞本质:检查时用的是第②次解码后的值(含 ? 可截断匹配白名单),而 include 用的是第①次解码后的值(%3F 仍是字面字符,作为普通路径组件参与目录遍历)。两次解码结果不同,造成了绕过空间。
寻找提示
首先访问 hint.php:
得到提示,flag 在名为 ffffllllaaaagggg 的文件中。
漏洞利用思路
利用检查3的双重URL编码绕过:
我们需要传入一个值
V,使得:urldecode(V)以hint.php?开头 → 截断后匹配白名单 → 通过检查3V本身是一个有效的路径,指向ffffllllaaaagggg
关键构造:
- 在URL中传入
hint.php%253F/../../../../ffffllllaaaagggg %25是%的URL编码,所以PHP一次解码后得到hint.php%3F/../../../../ffffllllaaaagggg- 这里
%3F是字面字符%、3、F,不是?
- 在URL中传入
检查过程逐级分析
检查1 — 直接 in_array:"hint.php%3F/../../../../ffffllllaaaagggg" 不在白名单中 → ❌
检查2 — 在 ? 处截断:mb_strpos("hint.php%3F/../../../../ffffllllaaaagggg?", "?")
字符串中没有字面 ?(%3F 是三个普通字符),找到的是追加在末尾的 ?
→ 截断后得到完整字符串,不在白名单中 → ❌
检查3 — URL解码后在 ? 处截断:urldecode("hint.php%3F/../../../../ffffllllaaaagggg")
→ "hint.php?/../../../../ffffllllaaaagggg"
→ 在 ? 处截断得到 "hint.php" → 在白名单中 → ✅ 通过!
Include 时的路径解析
include "hint.php%3F/../../../../ffffllllaaaagggg"
PHP 内部对包含路径做归一化处理,将 .. 作为目录回溯解析:
1 | /var/www/html/hint.php%3F/../../../../ffffllllaaaagggg |
hint.php%3F— 路径组件..→ 回到/var/www/html/..→/var/www/..→/var/..→/ffffllllaaaagggg→ 拼入路径
最终路径:/ffffllllaaaagggg → 成功读取 flag!
攻击过程

拿到 Flag:
1 | DASCTF{5edecbeb-fe51-49ba-9f80-6ed1ce06e32f} |
总结
这道题考察的是 白名单文件包含 + 双重URL编码绕过 的经典组合:
- 第一层:直接用
?截断可以绕过检查2,但include时?后的内容被丢弃,无法实现任意文件读取 - 第二层:检查3有额外的
urldecode,通过将?双重编码(%253F),让解码后的字符串能通过白名单检查,而原始值(一次解码后的%3F)在include时作为普通字符参与路径解析 - 利用 PHP 对
include路径的归一化机制,用../回溯目录,最终包含到根目录下的目标文件
这种绕过模式在 CTF 白名单文件包含题目中非常经典,核心就是利用 “解码前后的值不同” —— 检查时用解码后的值匹配白名单,包含时用解码前的值作为文件路径。
