参考【1】中四哥已经讲的很全面也很详细了,简单记录下复现过程吧
MySQL 反序列化
autoDeserialize
如解释,mysql client会自动反序列化server端传回的BLOB类型
复现
手动触发
1 | CREATE DATABASE IF NOT EXISTS test; |
其中@obj取值
1 | java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections7 "touch /tmp/cc7" | xxd -ps -c 200 | tr -d '\n' |
触发代码
1 | ResultSet rs = stmt.executeQuery("select evil_2 from eviltable limit 1;"); |
误区:想当然以为只要是BLOB类型的字段就会被自动反序列化,其实不然,需要满足以下两个条件
- autoDeserialize 为True
- 主动调用com.mysql.cj.jdbc.result.ResultSetImpl#getObject,进行反序列化
ServerStatusDiffInterceptor& detectCustomCollations触发调用链
手动触发场景下实际需要控制:
- jdbc url,也就是evil mysql server
- 代码需要getObject,这点显然不可能
那么需要找到利用链,解决触发getObject。可以看看四哥在参考【1】中总结的漏洞历史,总的来说有公开两种通用的利用方式
- ServerStatusDiffInterceptor
- detectCustomCollations
四哥和fnmsd的文章已经讲的很清楚了,这里直接引用他们的测试结果
ServerStatusDiffInterceptor
1 | /** |
detectCustomCollations
1 | /** |
思路分析
知其然更需知其所以然,下面尝试反向复现是如何发现这两个利用链的
autoDeserialize是源头,从autoDeserialize参数开始分析
1. autoDeserialize的调用情况
可以看到,如果字段类型是Binary或者BLOB,会进行反序列化,其中-84、-19为0xaced,java反序列化标志。
2. 反向追踪
1 | ClientPreparedStatement.EmulatedPreparedStatementBindings |
2.1. ClientPreparedStatement.EmulatedPreparedStatementBindings
PreparedStatement是预编译,还是只能在client 通过主动调用getObject触发,对利用无效
2.2. UpdatableResultSet
UpdatableResultSet为更新Result,未找到利用点
2.3. DatabaseMetaData.ResultSetIterator
尝试了几个链,都不太实用
1 | ConnectionImpl#setAutoCommit |
失败,ResultSetIterator是protect类,无法通过Class.forName(className).newInstance()实例化
1 | DatabaseMetaData#getTables/getCloumns等 |
触发困难
1 | ConnectionImpl#prepareCall |
触发困难
2.4. ResultSetUtil
有利用点,反向追溯过程如下
调用链
ResultSetUtil#resultSetToMap
1 |
|
ServerStatusDiffInterceptor#populateMapWithSessionStatusValues
1 | private void populateMapWithSessionStatusValues(Map<String, String> toPopulate) { |
此处了执行sql:SHOW SESSION STATUS
- evil mysql server可控情况下,可以返回可控的sql结果,从而触发后续的反序列化
- 如何触发populateMapWithSessionStatusValues,继续跟踪
发现ServerStatusDiffInterceptor#postProcess和#preProcess有两处调用
1 | ServerStatusDiffInterceptor |
到这里和结果已经很接近了
- 假如了解jdbc mysql 的参数,知道有个参数queryInterceptors,那对应起来很快就知道答案了
- 但是我是不了解的,只能苦逼的继续跟踪了
NoSubInterceptorWrapper#preProcess
1 | public <T extends Resultset> T preProcess(Supplier<String> sql, Query interceptedQuery) { |
NativeProtocol#invokeQueryInterceptorsPre
1 | public <T extends Resultset> T invokeQueryInterceptorsPre(Supplier<String> sql, Query interceptedQuery, boolean forceExecute) { |
NativeProtocol#sendQueryPacket
1 | public final <T extends Resultset> T sendQueryPacket(Query callingQuery, NativePacketPayload queryPacket, int maxRows, boolean streamResults, |
NativeSession#execSQL
1 | public <T extends Resultset> T execSQL(Query callingQuery, String query, int maxRows, NativePacketPayload packet, boolean streamResults, |
ConnectionImpl#setAutoCommit
1 |
|
抓包看过mysql协议的都清楚,login之后,client和server会沟通一些参数,其中就有autocommit,所以链路径上是没问题了,需要解决的是链上的诸多条件
queryInterceptors的初始化
其中重点是queryInterceptors取值问题,最开始出现在NativeProtocol#sendQueryPacket
1 | public <T extends Resultset> T invokeQueryInterceptorsPre(Supplier<String> sql, Query interceptedQuery, boolean forceExecute) { |
追溯发现
NativeProtocol#queryInterceptors
NativeSession#queryInterceptors
ConnectionImpl#queryInterceptors
最终定位是在initializeSafeQueryInterceptors进行的初始化
ConnectionImpl#initializeSafeQueryInterceptors
1 | public void initializeSafeQueryInterceptors() throws SQLException { |
而PropertyKey,可以通过jdbc url参数进行指定,问题解决。
小结
本节在老师傅们的文档基础上,手动复现了jdbc mysql connector 8.0.14 cc7的反序列化,以及不同版本在ServerStatusDiffInterceptor和detectCustomCollations 两种方式的反序列化复现,没有什么新的东西。
求渔得渔,后半部分试着反向去挖掘jdbc mysql反序列化的利用链,在8.0.14版本上发现了几个鸡肋的链,最终发现ServerStatusDiffInterceptor链,过程虽然稍长,不过也算是最大的收获。
老样子,抛几个问题
- 在反向找链的时候,发现本地还有一些类调用了ResultSetUtil.getObject,会不会有新的利用链
- Binary类型也有同样的问题,测试下?
- 目前看来autoDeserialize是强特征,好像没绕过这个特征姿势了