ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WarmUp 题学习笔记:一次 PHP 代码审计的记录

WarmUp 题学习笔记:一次 PHP 代码审计的记录

记录一下我做 HCTF 2018 WarmUp 这道题时的理解过程,主要是搞懂几个一开始卡住我的问题。

题目背景

打开靶机只有一个滑稽图。

查看源码发现注释提示source.php。访问后拿到源码,访问hint.php得到提示:flag 在ffffllllaaaagggg文件里。

核心代码(简化):

classemmm{publicstaticfunctioncheckFile(&$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/../../../,想用#当分隔符。错在两点:

  1. #在 URL 里是锚点标记,浏览器会把#后面的内容截掉不发给服务器。服务器只收到hint.php?,../全丢,include 找不到文件。
  2. 根本不需要#:?本身就是分隔符,后面直接接/../就行。

正确写法:hint.php?/../../../../ffffllllaaaagggg


完整 Payload 与结果

http://<靶机>/source.php?file=hint.php?/../../../../ffffllllaaaagggg

CTF2{6cf95f90-479b-4844-83dc-f388f6e9c43a}

../层数看靶机目录深度,本环境 4 层刚好,实际可多试几层。


一句话总结

用?骗 checkFile 说"我要访问 hint.php"(拿到 true 的门票),
但 include 真正读的还是那串带../的完整路径,
操作系统顺着../一层层往上爬,把根目录的 flag 读了出来。


学习记录,欢迎指正。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进