记录一下我做 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/../../../,想用 # 当分隔符。错在两点:

  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 读了出来。


学习记录,欢迎指正。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐