XCTF-warmup详细题解(含思考过程)

发布于:2025-08-15 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、题目来源

XCTF-Web-warmup

在这里插入图片描述

二、信息搜集

进入场景后,映入眼帘的是一张滑稽图

替代文本

ctrl+u看一下源码,查看是否存在“敏感信息泄露”的现象

在这里插入图片描述
果然存在,访问一下该文件:

http://ip:端口/source.php

可以看到出现了一段后端代码(用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\" />";
    }  
?>

三、代码审计

既然有了源码信息,我们就可以对代码进行分析

我们先不管上面的类,看到代码的主体部分,即:

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\" />";
} 

一眼就可以看到关键函数include,并且里面的参数是用户可控的(通过REQUEST传值)

因此,存在文件包含漏洞

但是要够着这个函数,需要满足三个判断条件,分别是

  • file参数不为空
  • file接受的参数需要是字符串
  • emmm类的checkFile方法其返回值需要为1

如果不满足,就会显示滑稽图,这也就是为什么我们一开始进入会出现滑稽的根本原因(没有传值给file)

好,现在我们就可以回过头去看emmm类了

该类中有且仅有一个方法,就是checkFile(),且该方法的参数值就是我们通过REQUEST传值给file的值

它首先列出来白名单列表:

$whitelist = ["source"=>"source.php","hint"=>"hint.php"];

到这其实就可以大致猜测出来该方法的用途,就是检查我们输入的文件名是否合法

那么,它列举出来的官方文件有且仅有:

  • source.php
  • hint.php

第一个文件我们看过了,直接看第二个文件

http://ip:端口/hint.php
或者直接:
http://ip:端口/?file=hint.php

可以看到一段提示:

flag not here, and flag in ffffllllaaaagggg

说明我们最终要找的flag存储在ffffllllaaaagggg文件中

大家不要因“无后缀”的问题而感到疑惑,在Linux操作系统中是不根据文件后缀名来判断文件类型的
因此,没有后缀名是常有的事情

但是,很明显,该文件是不属于白名单里面的

因此,我们继续分析后面的代码

if (! isset($page) || !is_string($page)) {
    echo "you can't see it";
    return false;
}

他会检测我们是否输入信息,如果没有或者不为字符串,就会直接退出且返回false

这点只要我们正常输入文件就不会符合该判断,接着看后面

if (in_array($page, $whitelist)) {
  return true;
}

会判断我们的输入信息是否在白名单内,如果在就返回true,否则继续

按照正常的逻辑来说,此次判断结束后会直接跟上一个:

return false;

直接发挥白名单真正的作用,但是它并没有这么做

而是会再经过几个判断(且该判断又返回true的希望)最后再到达false逻辑

替代文本

这就变相给了攻击者很多的机会

假设,我们现在输入就是ffffllllaaaagggg,当前这个判断是不满足的,会继续后面的逻辑,还有机会,我们接着看

在下一个判断之前,会多一个步骤:

$_page = mb_substr(
    $page,
    0,
    mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
    return true;
}

首先,我们来了解一下mb_substrmb_strpos两个函数

mb_strpos(
    string $haystack,
    string $needle,
    int $offset = 0,
    ?string $encoding = null
): int|false

该函数会查找$needle$haystack中第一次出现的位置(下标位置)

比如mb_strpos("abc","c");的返回值就是2

 mb_substr($str,$start,$length);

该函数会输出从$str$start下标开始的长度为$length的字符串

比如:

<?php
echo mb_substr("abc", 0, 2);
// 输出ab
?>

我们将我们的假想输入“ffffllllaaaagggg”带进去看一看

//根据输入改写代码成:
$_page = mb_substr(
    "ffffllllaaaagggg",
    0,
    mb_strpos('ffffllllaaaagggg?', '?')
);

最后的输出就是:ffffllllaaaagggg

很明显,经过转换后还是不能满足下述判断:

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;
}

与上一个判断不同的是,这里多出来了一个urldecode

其他的逻辑基本不变,无非是那两个函数的对象变成了"$_page"而不是原本的"$_page"

很明显,如果我们不改变我们的假想输入,那么结果还是一样

四、Payload构造

知道了检查文件的逻辑,我们就可以开始构造payload了

非常明确的几个细节就是:

  • payload中得有关键文件ffffllllaaaagggg
  • 得拿“?”做文章

我们观察到,每次使用函数mb_strpos它都会手动在末尾帮我们拼接(“.”)上一个“?”

换言之,只要我们输入的内容本身就携带"?"就会截断该问号前面的内容,保留后面的内容

而根据我们之前的分析,他们的判断都是针对"?"前面的内容进行判断的,但是include()的参数却是整体的内容

那么我们的payload可不可以是这样的形式:

payload = "白名单内容?xxxxx"

我们只要让其满足:

  • 前面用于白名单判断
  • 整体作为一个正确的文件路径,且能导向到文件ffffllllaaaagggg

因此,我们的payload由此而生:

payload = "source.php?/../../../../ffffllllaaaagggg"

这样的payload,虽然绕不过第一个判断,但是第二、第三个判断都是可以符合的

因为,根据判断逻辑,他只会截取第一个问号前面的数据,即source.php

因而,函数返回为true,使得该payload成功进入文件包含逻辑

而"source.php?"这个整体会被当成一个文件名作为文件路径的一部分

这里可能会有疑问
当我们输入:
http://ip:端口/?file=source.php?/../../../../ffffllllaaaagggg
的时候
浏览器会自动帮我们进行URL编码,但是在此处,编码后的结果还是其本身
在后端被file接受的时候,file接收到的还是“source.php?/../../../../ffffllllaaaagggg
这里大家也可以手搓测试代码验证一下(测试代码如下)

<?php
   $file = $_GET['file'];
   echo $file;
?>

结果:
在这里插入图片描述
为什么“?”也被作为我们输入内容的一部分呢?
这就是因为“?”在URL中的作用是:分隔路径与查询参数
因此这里的"?“很明显会被误以为source.php还有传输参数的必要,因此作为正常语法逻辑被传输进来了
但是后续我们通过在”?"后加上一个“/”把“source.php?”完整地作为了一个文件名而存在了

后面的“…/”就是回到上级目录,多个“…/”就可以让目录直接返回到根目录的位置,实现目录穿越的目的

因此我们只要构造URL为:

http://ip:端口/?file=source.php?/../../../../ffffllllaaaagggg

即可成功得到flag!

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到