起因
一个很简单的漏洞, 分析的文章也都出来了很多, 但是看了那些文章, 我一直搞不懂为啥问号要双重url编码, 我自己看那些文章感觉不编码应该也能成功利用的。 并且好像大家对问号编码的说法各有不同。 还是自己下载了份源码看了一下。
4.8.1下载地址:https://files.phpmyadmin.net/phpMyAdmin/4.8.1/phpMyAdmin-4.8.1-all-languages.zip
分析
在index.php中
1
2
3
4
5
6
7
8
9
10
|
// 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;
}
|
可以加载一些脚本。
直接看最后一个检测
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
|
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 .
'?',
'?')
);
// $page 是我们传递进来的要包含的文件
// 这里是截取到$page里?之前的文件名 并存入$_page中,
// 如果$_page, 在白名单之类的话, 直接通过, 所以这里我们不用双重编码也会return true.
// public static $goto_whitelist = array(
// 'db_datadict.php',
// 'db_sql.php',
// 'db_events.php',
// 随便从白名单的列表中选一个文件就行了。
if (in_array($_page, $whitelist)) {
return
true;
}
// 如果双重编码, 在下面这个流程里因为解码了, 也会return true
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page .
'?',
'?')
);
if (in_array($_page, $whitelist)) {
return
true;
}
return
false;
}
|
return true了, 就直接include了。
直接传 db_sql.php?/../robots.txt 就能成功包含到了。
不需要编码, 因为这时候 include ‘db_sql.php?/../robots.txt’
在include中, ?号后面的字符 并不会当成query,
问号也就只是路径的一部分而已。 所以不需要编码, 利用/../ 也能跳出目录。
Getshell的话, 根据p师傅的思路,执行sql语句后, 直接包含session文件就行。
1
2
3
4
5
6
7
|
$session_name = 'phpMyAdmin';
@session_name($session_name);
// Restore correct sesion ID (it might have been reset by auto started session
if (isset($_COOKIE['phpMyAdmin'])) {
session_id($_COOKIE['phpMyAdmin']);
}
|
phpmyadmin的session_name 是phpMyAdmin
执行的sql语句存入session的有两个位置。 一个就是sql_history。
/libraries/classes/Relation.php中
1
2
3
4
5
|
$_SESSION[
'sql_history'][] =
array(
'db' => $db,
'table' => $table,
'sqlquery' => $sqlquery,
);
|
这里的\$sqlquery 就是所执行的sql语句, 然后存入到了\$_SESSION当中。
修复
4.8.2中, 修复了这个漏洞。
1
2
3
4
5
6
7
8
9
|
if (! empty($_REQUEST['target'])
&& is_string($_REQUEST['target'])
&& ! preg_match('/^index/', $_REQUEST['target'])
&& ! in_array($_REQUEST['target'], $target_blacklist)
&& Core::checkPageValidity($_REQUEST['target'], [], true)
) {
include $_REQUEST['target'];
exit;
}
|
在调用checkPageValidity方法的时候, 第三个参数设置为了true
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
|
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;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
|
当第三个参数为true的时候, 在mb_substr之前, 就return false了。
所以, 现在真的只能包含白名单内的文件了。