前言

最近在hgame week4看到这么一个知识点,因为之前没有注意,现在仔细一看觉得很有意思,所以稍微研究学习一下。

利用场景

受害人连接了攻击者开启的恶意伪MySQL服务。

利用条件

受害人开启了local_infile或连接时加上了–enable-local-infile,且不使用SSL连接

利用效果

任意文件读取(受害人当前权限下)。

攻击原理

在交互中,MySQL允许服务端在客户端的任意一次Request Query后,回复一个Response TABULAR,这个Response包则会让客户端读取本地文件并发送。

攻击流程:

  • 伪MySQL服务等待客户端连接,然后返回一个Greeting
  • 客户端进行Login请求
  • 服务端不管怎么样,直接返回一个Response OK
  • 客户端进行版本查询
  • 服务端返回一个Response TABULAR
  • 客户端发送文件
  • 服务端返回一个Response OK
  • 客户端发送Request Quit,然后断开连接。

按照MySQL官网上说就是:

  • The transfer of the file from the client host to the server host is initiated by the MySQL server. In theory, a patched server could be built that would tell the client program to transfer a file of the server’s choosing rather than the file named by the client in the LOAD DATA statement. Such a server could access any file on the client host to which the client user has read access. (A patched server could in fact reply with a file-transfer request to any statement, not just LOAD DATA LOCAL, so a more fundamental issue is that clients should not connect to untrusted servers.)
  • In a Web environment where the clients are connecting from a Web server, a user could use LOAD DATA LOCAL to read any files that the Web server process has read access to (assuming that a user could run any statement against the SQL server). In this environment, the client with respect to the MySQL server actually is the Web server, not a remote program being run by users who connect to the Web server.

想要自行研究的可以:

tcpdump -i eth0/lo -w mysql.pcap port 3306

然后关闭本地MySQL的SSL,通过127.0.01的方式连接,执行SQL语句再退出,然后自行查看tcpdump抓到的流量包。

对于不想写脚本的我来说,自然是用的现成脚本啦:https://github.com/Gifts/Rogue-MySql-Server

脚本挺好用的,用法也很简单,直接python运行就可以了。就是我用的时候一直报错ERROR 2027 (HY000): Malformed packet,我查看log后发现是Greeting包的原因,所以我就直接修改了脚本里自带的Greeting包,改成了我本地的。


脚本研究

当然肯定不能一知半解啦,所以我们来研究一下脚本吧!

启动脚本会运行一个socket server,使用asyncore来进行异步通信,使用logging来进行通信过程的记录,同时通过daemonize进入后台运行,所以要看接收到的文件直接tail mysql.log就可以看到了。

因为开启socket服务的部分每种server都差不多,所以主要看一下asynchat的处理逻辑,push和collect_incoming_data也不多说,主要看__init__和found_terminator。

__init__的处理主要是设置终止长度为3(MySQL包的长度位基本都在这里)、用state标识这部分是包的长度、用sub_state标识这是Auth(Login)包的一部分、设置order防止数据包乱序,然后返回一个Greeting包。

然后看found_terminator函数,这是这个脚本的主要代码

self.state == ‘LEN’

按照小端字节序来计算包的长度,如果长度超出65536则直接中断socket。

elif self.state == ‘Data’:

包的数据部分,包的一个字节表示这是这一次通信的第几个包(值得一提的是Request Query包不遵从这一个顺序,它的序数是0),代码先检查一遍数据包是否乱序,如果乱序则抛出一个OutOfOrder警告。

packet.packet_num == 0

这说明这个包是Request包,然后再根据包的第二个字节来分辨这是个什么包并进行相应的处理,如果是\x03(Request Query包)就返回一个Response TABULAR包。

packet.packet_num != 0

有两种情况,这个包是Auth包或者File包,如果是Auth包就直接返回个Response OK包表示登录认证成功(不管账号密码正确与否),如果是File包则又有两种情况:长度为1,表示传输文件结束,则回复一个Response OK包;长度不为1,表示文件内容,则开始处理文件内容。


如果想要更加深入地研究脚本(或者研究MySQL通信),可以使用pwntools写一个脚本来进行交互,简单的交互脚本如下:

from pwn import *
context.log_level = 'debug'

target = remote("*.*.*.*", "3306")
target.recv()
payload = "\x0d\x00\x00"
payload += "\x01Twings Client"
pause()

target.send(payload)
target.recv()
payload2 = "\x12\x00\x00\x00\x03Give me the flag!"
pause()

target.send(payload2)
target.recv()
payload3 = "\x0d\x00\x00\x02Twings here!\n\x00\x00\x00\x03"
pause()

target.send(payload3)
target.recv()
payload4 = "\x01\x00\x00\x00\x01"
pause()

target.send(payload4)
target.interactive()
target.close()

参考:

https://lightless.me/archives/read-mysql-client-file.html

https://xz.aliyun.com/t/3973


Web MySQL

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

DNS-Rebinding
Wordpress5.0 RCE CVE-2019-8942复现