MySQL客户端任意文件读取
/0x00 前言
之前学习Jackson一条Gadget的时候涉及过,HW期间又发现类似的恶意利用工具和漏洞,就再补充一些细节。
0x01 MySQL客户端任意文件读取
LOAD DATA LOCAL INFILE
具体可参考:https://dev.mysql.com/doc/mysql-security-excerpt/5.7/en/load-data-local-security.html
MySQL支持使用LOAD DATA LOCAL INFILE语句,即可将客户端本地的文件中的数据insert到MySQL的某张表中。
注意,还有个LOAD DATA INFILE语句,这是加载服务端的文件而非客户端的。
LOAD DATA LOCAL INFILE的工作过程大致如下:
- 用户在客户端输入:load data local file “/data.txt” into table test;
- 客户端->服务端:我想把我本地的/data.txt文件插入到test表中;
- 服务端->客户端:把你本地的/data.txt文件发给我;
- 客户端->服务端:/data.txt文件的内容;
测试看下LOAD DATA LOCAL INFILE语句,用Kali作为客户端远程连接MySQL服务,然后执行如下SQL语句来将/etc/passwd
文件中的内容插入到MySQL的users表中:
1 | load data local infile "/etc/passwd" into table users FIELDS TERMINATED BY '\n'; |
漏洞原理
上述过程存在一个问题,即客户端发送哪个文件的内容,取决于第三步即服务端响应的想要的哪个文件,如果服务端是个恶意的MySQL,那么它可以读取客户端的任意文件内容,比如读取/etc/passwd:
- 用户在客户端输入:load data local file “/data.txt” into table test;
- 客户端->服务端:我想把我本地的/data.txt文件插入到test表中;
- 服务端->客户端:把你本地的/etc/passwd文件发给我;
- 客户端->服务端:/etc/passwd文件的内容;
而且,在大部分客户端(比如MySQL Connect/J)的实现里,第一步和第二部并非是必须的,客户端发送任意查询给服务端,服务端都可以返回文件发送的请求。而大部分客户端在建立连接之后,都会有一些查询服务器配置之类的查询,所以使用这些客户端,只要创建了到恶意MySQL服务器的连接,那么客户端所在的服务器上的所有文件都可能泄露。
注意:如果使用MySQL客户端直接连接的话,是需要添加--enable-local-infile
选项的,而其他大部分MySQL客户端实现中,是默认开启的,比如allowLoadLocalInfile是MySQL的JDBC驱动的一个创建连接的配置项、用来控制是否允许从本地读取文件,默认值为True,具体的MySQL客户端实现得具体看。
漏洞复现
攻击流程如下:
- 攻击者开启伪造的恶意MySQL服务器,诱使受害者MySQL客户端访问;
- 受害者向恶意MySQL服务器发起请求,并尝试进行身份认证;
- 恶意MySQL服务器接受到受害者的连接请求后,发送正常的问候、身份验证正确并且发送LOAD DATA LOCAL INFILE语句来读取受害者客户端本地敏感文件;
- 受害者的MySQL客户端认为身份验证正确,执行攻击者的发来的请求,通过LOAD DATA LOCAL INFILE语句将本地文件内容发给恶意MySQL服务器;
- 恶意MySQL服务器接受到客户端敏感文件,Done;
GitHub上恶意MySQL服务相关项目:
开启恶意MySQL服务后,受害者尝试使用MySQL客户端连接恶意服务端(这里metasploitable机子的MySQL版本为5.0.51a-3ubuntu5,无需添加--enable-local-infile
选项即可成功):
恶意MySQL服务端窃取到了/etc/passwd
文件内容:
解决方案
针对LOAD DATA LOCAL INFILE的安全问题,MySQL官方给出如下说明:
为了避免连接到不受信任的服务器,客户端可以建立安全连接并通过使用
--ssl-mode=VERIFY_IDENTITY
选项和适当的CA证书进行连接来验证服务器身份 。为避免出现
LOAD DATA
问题,客户应避免使用LOCAL
。管理员和应用程序可以配置是否允许本地数据加载,如下所示:
在服务器端:
- 所述
local_infile
系统变量控制服务器端LOCAL
的能力。根据local_infile
设置,服务器会拒绝或允许请求本地数据加载的客户端加载本地数据。- 默认情况下,它
local_infile
是禁用的。要显式地使服务器拒绝或允许LOAD DATA LOCAL
语句(无论在构建时或运行时如何配置客户端程序和库),请在 禁用或启用的情况下启动mysqldlocal_infile
。local_infile
也可以在运行时设置。在客户端:
该CMake的选项控制编译默认的MySQL客户端库能力(见 MySQL源代码的配置选项)。因此,未进行明确安排的客户端将 根据MySQL构建时指定的设置禁用或启用功能 。
ENABLED_LOCAL_INFILE
LOCAL`
LOCAL[
ENABLED_LOCAL_INFILE`](https://dev.mysql.com/doc/refman/5.7/en/source-configuration-options.html#option_cmake_enabled_local_infile)默认情况下,MySQL二进制发行版中的客户端库在
ENABLED_LOCAL_INFILE
启用时进行编译 。如果从源代码编译MySQL,请ENABLED_LOCAL_INFILE
根据未进行显式安排的客户端应LOCAL
禁用还是启用功能,将其配置为禁用或启用。对于使用C API的客户端程序,本地数据加载功能由编译到MySQL客户端库中的默认值决定。要显式启用或禁用它,请调用
mysql_options()
C API函数以禁用或启用该MYSQL_OPT_LOCAL_INFILE
选项。参见 mysql_options()。对于mysql客户端,本地数据加载能力由编译到MySQL客户端库中的默认值决定。要显式禁用或启用它,请使用
--local-infile=0
或--local-infile[=1\]
选项。对于mysqlimport客户端,默认情况下不使用本地数据加载。要显式禁用或启用它,请使用
--local=0
或--local[=1\]
选项。如果
LOAD DATA LOCAL
在Perl脚本或其他[client]
从选项文件中读取该组的程序中使用,则可以向该组添加local-infile
选项设置。为防止不理解此选项的程序出现问题,请使用loose-
前缀指定它 :
1
2
3 > [client]
> loose-local-infile=0
>
或者:
1
2
3 > [client]
> loose-local-infile=1
>
- 在所有情况下,
LOCAL
客户端成功使用加载操作还需要服务器允许本地加载。如果
LOCAL
禁用了此功能,则在服务器或客户端上,尝试发出LOAD DATA LOCAL
语句的客户端都会 收到以下错误消息:
1
2 > ERROR 1148: The used command is not allowed with this MySQL version
>
0x02 MySQL蜜罐
利用该漏洞可以制作MySQL蜜罐来诱使攻击者连接,从而窃取攻击者主机上的敏感信息。GitHub上已有可以读取攻击者微信ID的MySQL蜜罐,具体参见:https://github.com/qigpig/MysqlHoneypot