MySQL JDBC 反序列化漏洞分析


MySQL JDBC 反序列化漏洞分析复现环境复现准备漏洞简介JDBC漏洞分析参考文章


复现环境

java 1.8.0_282

mysql-connector-java 8.0.13

 

复现准备

引入mysql-connector-java

 

test.java(一个利用com.mysql.cj.jdbc.Driver连接并操作MySQL数据库的例子)

(PS. getConnection连接数据库阶段就存在MySQL任意文件读取漏洞,因为在connect阶段会读取MySQL Server的一些变量信息,产生了查询。利用工具:Rogue-MySql-Server)

 

漏洞简介

如上面代码中的String DB_URL参数可控时,可能造成反序列化漏洞

其漏洞触发在getConnection()时,也就是说后续操作数据库啥的不需要都能触发反序列化漏洞。

 

JDBC

JDBC一般指Java数据库连接。Java数据库连接,(Java Data Base Connectivity,简称JDBC)

是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

 

漏洞分析

看看为什么在连接过程中会出现反序列化漏洞。

反序列化点(readObject())在com.mysql.cj.jdbc.result.ResultSetImplpublic Object getObject(int columnIndex)switch条件语句中存在两处,一处是case BIT,另一处是case BLOB

可以看到都存在readObject()

但是getObject()的参数是int,它是怎么反序列化的?

首先需要知道getObject()的作用(参见文档https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-type-conversions.html):

ResultSet.getObject()方法使用MySQL和Java类型之间的类型转换

BITBLOB都是MySQL里的一种数据格式,查看其他case样例,也都是一些数据类型,判断后处理的作用也正是转化成java中的类型

BLOB

BLOB为二进制形式的长文本数据,大小是0-65535 bytes

BIT

Bit数据类型用来存储bit值 BIT(M)代表可以存储M个bit,M的取值范围为1到64 如果手工指定bit值,则可以使用b'value'格式,比如b'111'和 b'10000000'分别代表7和128

知道了getObject()是做啥的,再来看一下函数逻辑,

代码中是通过field.getMysqlType()来判断不同的类型,field来自this.columnDefinition.getFields()[columnIndexMinusOne];,差不多意思就是获取某一列的定义

如果为BIT或者BLOB

判断field是否为BinaryBlob后再用getBytes()获取其值,接着判断PropertyKey.autoDeserialize的值是否为真,为真则进入else分支

data满足第一位为-84第二位为-19就可以执行data的反序列化

-84 -19其实就是AC ED (0xAC == 256 - 84, 0xED == 256 - 19),就是Java的序列化内容的魔术头

 

触发getObject()的入口在com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues()中的ResultSetUtil.resultSetToMap(toPopulate, rs);

执行SHOW SESSION STATUS后并处理其结果时

处理返回结果时调用到getObject()

populateMapWithSessionStatusValues()com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptorpostProcess()preProcess()方法中调用

问题又来了,怎么调用?上面代码中可以看到

实现了QueryInterceptor

查阅官方文档得知

其可以通过配置属性来实现调用,也就是通过传入JDBC url时候的参数调用

值得一提的是,上文中提到的进入某else语句的条件是autoDeserialize必须为真,这个值也可以通过配置属性来实现

所以目前可以得知payload中不可或缺的一段为:

先利用mysql8调试发现确实能进入到这个分支,只不过暂时mysqlTypeVARCHAR类型:

可以到wireshark中查看流量信息(需配置useSSL=false属性,不然都是TLS流量)

跳过身份认证阶段,前面的3个SELECTSET语句是getConnection()时就会触发的操作,然后通过我们配置的autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor属性成功触发populateMapWithSessionStatusValues()中的SHOW SESSION STATUS

其实用脚本也挺好构造的,直接把wireshark中的流量复制一下,修改一下SHOW SESSION STATUS语句后的返回结果就好

有一种简单构造语句的方法,直接在mysql中创建一个table,并添加

然后抓包获取查询的此数据库的流量数据

替换上面SHOW语句的返回结果就可以触发反序列化了。

(理论上直接修改SHOW SESSION STATUS所用到的表performance_schema.session_status就行)

 

 

可以看到最新版(8.0.23)中com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues()代码已修改,SHOW SESSION STATUsS后没有了ResultSetUtil.resultSetToMap(toPopulate, rs);的操作:

 

 

参考文章

JDBC详解

MySQL JDBC 客户端反序列化漏洞分析

小白看得懂的MySQL JDBC 反序列化漏洞分析

https://dev.mysql.com

MySQL数据类型

MySQL 数据类型之bit类型