WarmUp

进来一个滑稽表情包,查看页面源码,得到提示

<-- source.php -->

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

审计一下php代码,需要使用GET方式提交一个 $file 变量,进行文件包含。

看到白名单,随即访问hint.php,得到ffffllllaaaagggg文件名。

多次if语句判断,第一次判断是否存在 $page 变量且是否为字符串,简单绕过。

第二层 if 用到了bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) 函数:

in_array($page, $whitelist)

如果满足了就 return true。关于 bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) 函数有它的设计缺陷,当进行弱比较的时候会进行弱类型转换,从而绕过对数字的审查。大致可以看这篇参考文章:PHP代码审计Day1 - in_array函数缺陷

在这里由于数组类型是字符串类型,所以不存在这样的弱类型转换漏洞,这里如果需要返回 true,则必须 $page 的值为 hint.php 或者 source.php。

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

这部分用到了两个比较新的 php 函数:

mb_substr ( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] ) : string

substr() 函数不同的是,mb_substr() 通常用来分割多种字符,用于兼容字符集。例如:

echo mb_substr("Hello, world!", 0, 2);
 //echo:He
echo mb_substr("你好世界!", 0, 2);
 //echo:你好

这看起来似乎更符合中国人的习惯。

mb_strpos ( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]] ) : int

该函数用来返回字符串 $needle$haystack 字符串中第一次出现的位置。这里就存在绕过,只要题目中的字符串中出现问号,就可以对字符串进行截断,从而绕过白名单的审查。

echo mb_strpos("Hello!" . "?", "?"); //echo: 6
echo mb_strpos("?Hello!") . "?", "?"); //echo: 0

于是只要构造这样的字符串:

?file=hint.php?
?file=source.php?

就可以绕过白名单审计,返回 true

接下来就是简单的目录穿越完事,可以先读取 /etc/passwd:

source.php?file=hint.php?../../../../../etc/passwd

穿越回根目录经过五次,大概推测文件结构,然后读取flag。

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

Last updated