HCTF 2018 WarmUp 1(代码审计)

发布于:2025-06-17 ⋅ 阅读:(13) ⋅ 点赞:(0)

题目

在这里插入图片描述

做法

启动靶机,点进去
在这里插入图片描述

没啥信息,点开F12康康源码
在这里插入图片描述

发现有被注释的
在URL后面加进去被注释的东西,访问一下
在这里插入图片描述

分析一波

1. 代码起始和高亮显示

php

<?php
    highlight_file(__FILE__);
  • <?php:PHP 代码的起始标记。
  • highlight_file(__FILE__)
    • highlight_file() 是 PHP 内置函数,用于高亮显示文件的源代码(以 HTML 格式展示)。
    • __FILE__ 是 PHP 的魔术常量,表示当前文件的完整路径
    • 这行代码会把整个 PHP 文件的源代码显示在浏览器中(通常用于调试)。
2. 定义类和检查函数

php

    class emmm
    {
        public static function checkFile(&$page)
        {
  • class emmm:定义一个名为 emmm 的类(类似 Java/C++ 中的类)。
  • public static function checkFile(&$page)
    • public:函数可以被外部访问。
    • static:函数属于类本身,无需创建对象即可调用(如 emmm::checkFile())。
    • 静态方法checkFile用于检查文件
    • &$page引用传递参数,直接修改原始变量的值(这里未实际修改)。
3. 白名单设置

php

            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
  • 创建一个关联数组(类似 Python 的字典),白名单中只允许两个文件:
    • source.php
    • hint.php
4. 检查参数是否合法

php

            if (! isset($page) || !is_string($page)) {
                echo "you can't see it";
                return false;
            }
  • isset($page):检查变量 $page 是否存在且不为 NULL
  • is_string($page):检查变量 $page 是否为字符串类型。
  • 如果 $page 不存在或不是字符串,输出错误信息并终止检查。
5. 第一次白名单检查

php

            if (in_array($page, $whitelist)) {
                return true;
            }
  • in_array($needle, $haystack):检查 $needle 是否在 $haystack 数组中。
  • 如果 $page 直接等于 source.phphint.php,允许访问。
6. 截取问号前的内容再检查

php

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
  • mb_strpos($page . '?', '?')
    • mb_strpos() 查找字符串中第一次出现 ? 的位置。
    • $page . '?' 在字符串末尾添加 ?,防止找不到 ? 时返回 NULL
  • mb_substr($page, 0, ...):截取 $page 从开头到 ? 之前的部分。
  • 例如:$page = "source.php?xxx"$_page = "source.php"
  • 如果截取后的内容在白名单中,允许访问。
7. URL 解码后再次检查

php

            $_page = urldecode($page);
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
  • urldecode($page):对 URL 编码的字符串进行解码(例如 %20 → 空格)。
  • 再次截取解码后的字符串中 ? 之前的部分,检查是否在白名单中。
  • 例如:$page = "source.php%3Fxxx"%3F? 的 URL 编码)→ 解码后 $_page = "source.php"
8. 检查失败处理

php

            echo "you can't see it";
            return false;
        }
    }
  • 如果所有检查都不通过,输出错误信息并返回 false
9. 主程序:接收用户输入并包含文件

php

    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\" />";
    }  
?>
  • $_REQUEST['file']:获取用户通过 URL 参数(如 ?file=xxx)或 POST 表单提交的 file 参数。
  • empty():检查变量是否为空(值为 NULL''0 等)。
  • is_string( ):检查变量是否为字符串类型。
  • emmm::checkFile($_REQUEST['file']):调用之前定义的检查函数。
  • include $_REQUEST['file']
    • include 是 PHP 的文件包含函数,将指定文件的内容嵌入到当前脚本中执行
      即先暂停主脚本的运行,先运行子脚本
      (1)若内容是php的话,直接运行此php文件,完成后再跳出来继续运行主脚本(假设include里的脚本为子脚本)
      (2)若里面含有echo等输出函数、被引入文件包含 HTML 或非 PHP 内容(eg.纯文本内容)、return、被引入文件包含错误或警告信息等,则会直接读取并输出相应内容
    • 风险:如果检查函数存在漏洞,攻击者可能通过构造特殊的 file 参数读取任意文件。
  • exit:终止脚本执行,防止后续代码被执行。
  • 如果条件不满足,显示一张默认图片。

综上,我们为了绕过上面的保护,只需要在URL后写入上面提到hint.php(自行尝试,source.php那个我试了不行,没有提示我们要的flag在哪)
在这里插入图片描述

得出,我们要的flag就在ffffllllaaaagggg这里

至此,我们编写payload

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

解释:

1、为什么php后要加“ ?”呢:

(1)为了应付上面的检查,不然它就会在ffffllllaaaagggg后面加一个?我们就无法通过检测
(2)为了应付include函数,如下

2、include函数解释(以下通过上面给的第一个payload加以解释)

第一步:绕过白名单

传进 file= 的是:
hint.php?/../../../../ffffllllaaaagggg
PHP 会提取 ? 前的部分(hint.php),发现它在白名单中,就放行了。

第二步:PHP include 怎么处理这个路径?

include('hint.php?/../../../../ffffllllaaaagggg');
这是 PHP 的语法。

PHP 遇到 include("xxx?yyy") 的时候,只会 include xxx 这个文件,不会理会 ?yyy
所以,PHP 实际执行的是:
include("hint.php");

到这里你是读取 hint.php 并执行它了!

第三步:hint.php 的作用

虽然你没看到 hint.php 的源码,但你可以合理推测它会这样写
<include($_SERVER['QUERY_STRING']);// 它把 ? 后面的东西当成路径去 include>
你不知道你要找的flag在哪个文件里,一定会在这俩白名单文件里吗?谁也说不准,最简单的方法就是回到根目录进行查找了

你传的这个:
source.php?file=hint.php?/../../../../ffffllllaaaagggg
会让 hint.php 里的 $_SERVER['QUERY_STRING'] 变成:
/../../../../ffffllllaaaagggg
于是 hint.php 执行了:
include("/../../../../ffffllllaaaagggg"); // 最终解析为 /ffffllllaaaagggg
第二次 include() 是在 hint.php 中执行的,相对路径会从 hint.php 所在的目录 /var/www/html 开始解析,层层 ../ 向上,最终到达根目录 /,再拼上目标文件名,形成有效路径。

详细一点:
这里的include是这样工作的——假如hint.php的绝对路径是/var/www/html/hint.php,然后…/就会变成/var/www/html,再…/就会继续返回上一级/var/www,以此类推,最终返回根目录,即是/ffffllllaaaagggg了,就像你直接在你的c盘找一个文件一样,你不知道它放在哪,因此直接返回根目录一搜,即可搜到,因此,其实上面的payload有多少个…/都没什么问题,但是一定不能少,根目录再上一级还是停留在根目录上,不会再改变

那还有一个问题,就是payload里的…/最前面的/还有必要加吗?
我这边尝试了一下,发现去掉这个/还是可以得出flag,大概是无所谓的
但这里建议还是带上为好,因为带 / 更安全更规范,防止被解析成查询参数而不是路径字符串,从而导致我们得不到flag而去怀疑自己思路,导致更进一步的错误

如果没有那个 / 会怎样?(URL上需要加/)

如果你传:
hint.php?../../../../ffffllllaaaagggg
PHP 会以为你传的是个 GET 参数名叫 ../../../../ffffllllaaaagggg,值为空:
在不同 PHP/服务器组合中,有的解析器只有以 / 开头才会处理为路径
因此,这里强烈建议加上/,这里指的是URL上的

类型 意义
路径 控制服务器访问哪个资源
参数 把值传给正在运行的脚本

要是识别成参数,就变成是写进去东西了,而不是访问/找某个文件,跟我们的目的相悖了

但在include函数上呢,则恰恰相反了,是不建议加/的

它截取的是payload的?后面的,刚好把/截取到
一般来说,有/的是绝对路径,没有/的是相对路径

假设你的文件在这里:
/var/www/html/hint.php
你想 include:
/flag
那你可以这样:
include("../../../../flag"); // 从 hint.php 的目录出发 // 即:/var/www/html/../../../../flag = /flag
你也可以:
include("/flag"); // 绝对路径
都没问题。

但是你如果写:(可以,但没必要)
include("/../../../../flag"); // 从根目录开始,还向上跳❓
PHP 实际等价于:
/../../../../flag => /flag
虽然结果一样,但这写法:

  • 没有逻辑意义:根目录不能再往上跳
  • 容易引起误解或报错
  • 在不同系统/配置上行为不一致(特别是 open_basedir 或安全策略存在时)

那还有一个问题,就是既然上面说的

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

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

都可以得出flag,那我直接

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

行不行呢,上面说了嘛,既然直接从根目录出发这样写都可以,那我何必还要写这么多…/呢
直接/,一步到位,岂不美哉?

但答案是不行的,至少在该题中找不到flag

include("/flag")

大概是路径解析过程中触发了安全限制/被限制了,因此行不通

include("/../../flag")

一般是失败的,跟上面一样
(/…/…/flag其实跟/flag一样的,"/“继续往上还是”/"因此后面的…/其实没什么作用)

但如果路径被错误解析,则可能绕过限制(就像最前面没加/的情况一般)
因此,这种情况行不通的情况下,我们就得去掉最前面的/了

一旦路径以 / 开头,就告诉 PHP:
“我给的是绝对路径,不用管当前文件在哪。”

  • some/path/to/file → 相对路径(从当前工作目录出发)
  • /some/path/to/file → 绝对路径(从根 / 出发)

没办法了,找了半天,最合理的解释也只能是这样了【单纯指的include(“/flag”)和include(“/…/…/flag”)】,平时做题/比赛的时候不行就按照上面的思路一个一个慢慢试吧

得出flag
在这里插入图片描述

复制,回去提交
在这里插入图片描述

更新

于2025.06.16


网站公告

今日签到

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