[HCTF 2018]WarmUp

[HCTF 2018]WarmUp

解题步骤

开启靶场页面上只有一个大滑稽脸,没有什么特别有用的信息,直接查看源码

image

看到提示,网址上拼接url访问/source.php



代码审计

为了方便看,先单独一段一段分析
<?php
highlight_file(__FILE__);    // 高亮显示
class emmm
{
    public static function checkFile(&$page)
    {
        $whitelist = ["source"=>"source.php","hint"=>"hint.php"];     // 白名单
        if (! isset($page) || !is_string($page)) {                    // 参数检查
            echo "you can't see it";                                  // 输出提示信息
            return false;                       // 退出
        }

        if (in_array($page, $whitelist)) {      // 白名单检查
            return true;                        // 允许访问
        }

        $_page = mb_substr(                     // 去除参数
            $page,
            0,
            mb_strpos($page . '?', '?')         // 找到问号位置
        );
        if (in_array($_page, $whitelist)) {     // 白名单检查
            return true;                        // 允许访问
        }

        $_page = urldecode($page);              // 解码
        $_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'])                  // 参数检查
    && is_string($_REQUEST['file'])             // 参数类型检查
    && emmm::checkFile($_REQUEST['file'])       // 调用方法
) {
    include $_REQUEST['file'];         // 包含文件
    exit;                              // 退出
} else {                               // 参数错误
    echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";      // 输出滑稽脸
}
?>

public static function checkFile(&$page)    // 静态方法
    {
        $whitelist = ["source"=>"source.php","hint"=>"hint.php"];    // 白名单

先是定义了一个名为 checkFile 公共的静态方法,主要用于对传入的 $page 变量进行初步的合法性检查,定义了一个白名单数组,包含两个键值对,为允许访问的文件白名单

if (! isset($page) || !is_string($page)) {    // 参数检查
            echo "you can't see it";          // 提示信息
            return false;                     // 退出
        }

这段代码是文件访问控制的 “前置检查”,先验证 $page 的基本合法性(是否存在、是否为字符串),如不存在或不是字符串则输出提示并返回false

if (in_array($page, $whitelist)) {    // 白名单检查
        return true;    // 允许访问
}

检查传入的 $page 是否在白名单当中,是则返回true

$_page = mb_substr(                     // 去除参数
            $page,
            0,
            mb_strpos($page . '?', '?')         // 找到问号位置
        );

1. 核心函数解析

  • mb_strpos($str, $needle):多字节安全的字符串查找函数,返回$needle(目标字符)在$str(字符串)中首次出现的位置(索引值,从 0 开始)。若未找到,返回false。

  • mb_substr($str, $start, $length):多字节安全的子字符串截取函数,从$str的$start位置(默认 0)开始,截取长度为$length的子字符串。

2. 代码执行逻辑
$page . '?'表示在 $page 的末尾拼接一个问号?。
这样做的目的是:即使原始$page中没有?,也能确保拼接后的字符串一定包含?,避免mb_strpos返回false(确保后续截取长度有效)。

mb_strpos($page . '?', '?')查找拼接后字符串中第一个?出现的位置
(如$page = "hint.php"),则返回拼接后 ? 的位置(即$page的长度,例如"hint.php"长度为 8,拼接后?在索引 8 的位置)

mb_substr($page, 0, 上述位置)从 $page 的开头(0 位置)开始,截取到第一个?出现的位置(不包含 ? 本身),结果赋值给 $_page

这段代码通常用于截取文件名,去除可能包含的查询参数(?及之后的内容),只保留基础文件名。结合前面的白名单检查逻辑,这样可以避免因$page带有参数(如source.php?x=1)而导致白名单验证失败,确保只验证文件名本身是否合法

后面就是在检查一次在不在白名单里,继续重复文件名截取再检查白名单,只不过多了一个url解码,影响不大

$_page = urldecode($page);

接着解释最后一段

if (! empty($_REQUEST['file'])    // 参数检查
    && is_string($_REQUEST['file'])    // 参数类型检查
    && emmm::checkFile($_REQUEST['file'])    // 调用方法
) {
    include $_REQUEST['file'];    // 包含文件
    exit;    // 退出
} else {    // 参数错误
    echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";      // 输出滑稽脸
}

! empty($_REQUEST['file']):检查请求中是否存在名为file的参数,且该参数不为空。

is_string($_REQUEST['file']):确保file参数的值是字符串类型。这是基础的类型校验,防止非字符串类型的输入(如数组)导致后续处理异常

emmm::checkFile($_REQUEST['file']):调用emmm类的静态方法checkFile,对file参数的值进行进一步安全验证。只有checkFile返回true(验证通过),这个条件才成立。

include $_REQUEST['file']:包含并执行file参数指定的文件。include是 PHP 的文件包含语句,会将目标文件的内容当作 PHP 代码执行。



构造payload

理解完上述代码后,可以明确得出这是一个文件包含漏洞,文件包含分别有本地包含和远程包含

本地文件包含漏洞(LFI):指包含本地服务器中的文件

?file=../../../xxxx

远程文件包含漏洞(RFI):指包含远程服务器中的文件

?file=http://xxxx.xxx/xxxx

因为我们目前还不知道要包含的文件是不是直接叫flag,所以我们先尝试包含一下hint.php文件有没有多余提示,回显flag not here, and flag in ffffllllaaaagggg,很好直接得到flag所在的文件名

source.php?file=hint.php

接下来只需要绕过主要的三个判断就可以完美的构造payload了

1.判断我们传入的file值是否为空且是否为字符串
2.白名单判断传入的值是否在白名单 (source.php hint.php) 当中
3.将我们传入的值截取后再次进行白名单检测

得知我们传入的值必须在白名单当中,且需要绕过文件名截取,只需将file值的前面主动拼接一个souce.php?或者hint.php?,截取后的值依旧是source.php或hint.php,此时checkFile返回true就可以绕过检测

因为我这边是直接知道flag在根目录,通常flag也会放在根目录,linux系统的网页路径又一般为/var/www/html/文件名称,所以直接包含根目录下的文件,如果不清楚的情况下可以一个个去包含尝试

得出payload为

file=hint.php?/../../../../ffffllllaaaagggg

完整payload,以下随便一个都可以

source.php?file=source.php?/../../../../ffffllllaaaagggg

source.php?file=hint.php?/../../../../ffffllllaaaagggg

image

最终获取flagflag{467d5f45-294e-46b7-a15a-d021e0bceced}

posted @ 2025-10-01 02:12  云懿  阅读(19)  评论(0)    收藏  举报