HCTF 2018 WarmUp 1(代码审计)
本文最后更新于 8 天前,其中的信息可能已经有所发展或是发生改变。

题目

做法

启动靶机,点进去

没啥信息,点开F12康康源码
发现有被注释的 在URL后面加进去被注释的东西,访问一下
分析一波

1. 代码起始和高亮显示

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

2. 定义类和检查函数

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

3. 白名单设置

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

4. 检查参数是否合法

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

5. 第一次白名单检查

            if (in_array($page, $whitelist)) {
                return true;
            }
  • in_array($needle, $haystack):检查 $needle 是否在 $haystack 数组中。
  • 如果 $page 直接等于 source.php 或 hint.php,允许访问。

6. 截取问号前的内容再检查

            $_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 解码后再次检查

            $_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. 检查失败处理

            echo "you can't see it";
            return false;
        }
    }
  • 如果所有检查都不通过,输出错误信息并返回 false

9. 主程序:接收用户输入并包含文件

    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'' 等)。
  • 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

复制,回去提交

感谢阅读!如果你觉得这篇文章对你有帮助,欢迎扫码赞赏支持,你的鼓励是我持续创作的动力 ❤️

本文为原创内容,转载请注明出处。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇