安全矩阵

 找回密码
 立即注册
搜索
查看: 1207|回复: 0

从JDBC attack到detectCustomCollations利用范围扩展

[复制链接]

189

主题

191

帖子

903

积分

高级会员

Rank: 4

积分
903
发表于 2022-8-17 23:20:34 | 显示全部楼层 |阅读模式
从JDBC attack到detectCustomCollations利用范围扩展

文章链接:从JDBC attack到detectCustomCollations利用范围扩展 (qq.com)
漏洞分析原理POC
  1. 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方法,形成利用链
  1. //populateMapWithSessionStatusValuesprivate void populateMapWithSessionStatusValues(Connection connection, Map<String, String> toPopulate) throws SQLException {
  2.     java.sql.Statement stmt = null;
  3.     java.sql.ResultSet rs = null;

  4.     try {
  5.         toPopulate.clear();

  6.         stmt = connection.createStatement();
  7.         rs = stmt.executeQuery("SHOW SESSION STATUS");
  8.         Util.resultSetToMap(toPopulate, rs); //调用getObject方法
  9.     } finally {
  10.         if (rs != null) {
  11.             rs.close();
  12.         }

  13.         if (stmt != null) {
  14.             stmt.close();
  15.         }
  16.     }}
复制代码

  1. //Util.resultSetToMappublic static void resultSetToMap(Map mappedValues, java.sql.ResultSet rs) throws SQLException {
  2.     while (rs.next()) {
  3.         mappedValues.put(rs.getObject(1), rs.getObject(2));
  4.     }}
复制代码

同样在preProcess方法中也调用了
在调用链中也可以得到
  1. public ResultSetInternalMethods postProcess(String sql, Statement interceptedStatement, ResultSetInternalMethods originalResultSet, Connection connection)
  2.         throws SQLException {

  3.     if (connection.versionMeetsMinimum(5, 0, 2)) {
  4.         //调用
  5.         populateMapWithSessionStatusValues(connection, this.postExecuteValues);

  6.         connection.getLog().logInfo("Server status change for statement:\n" + Util.calculateDifferences(this.preExecuteValues, this.postExecuteValues));
  7.     }

  8.     return null; // we don't actually modify a result set}
复制代码
com.mysql.jdbc.ConnectImpl#loadServerVariables方法存在需要执行一段SHOW VARIABLES的sql语句
  1. 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的调用,能够形成前面所描述的利用链

编辑

首先看一下调用栈
  1. buildCollationMapping:1004, ConnectionImpl (com.mysql.jdbc)initializePropsFromServer:3617, ConnectionImpl (com.mysql.jdbc)connectOneTryOnly:2550, ConnectionImpl (com.mysql.jdbc)createNewIO:2320, ConnectionImpl (com.mysql.jdbc)<init>:834, ConnectionImpl (com.mysql.jdbc)<init>: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
只要满足上述条件,就只需要将结果集中的字段 2 或者 3 封装我们的序列化数据就可以成功利用了
进行探索过程在大佬的研究中,提到了,detectCustomCollations触发方式在5.1.40版本之后不能够利用,因为没有使用getObject的方式获取SHOW COLLATION的结果
但是在我的跟踪中,发现了,其实还是可以利用的,虽然这个发现没有什么大用,在高版本不能使用ServerStatusDiffInterceptor的时候用用??
在idea中添加对应版本的包
  1. <dependency>
  2.   <groupId>mysql</groupId>
  3.   <artifactId>mysql-connector-java</artifactId>
  4.   <version>5.1.41</version></dependency>
复制代码

在版本对比中,的确在新版本中删掉了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而不能够触发成功
            https://github.com/mysql/mysql-c ... 4a15fb47dcaL779-L78


编辑


           
  •         5.0.x没有这个拦截器

detectCustomCollations

           
  •         8.0.x不存在getObject方法的调用

           
  •         6.x能够利用,因为他在com.mysql.cj.jdbc.ConnectionImpl中调用了ResultSetUtil.resultSetToMap和上面的功能类似,且没有版本判断


编辑
从

编辑

在这里值得注意的是,在5.1.41做出了更改,不再调用Util.resultSetToMap方法,进而调用getObject方法,改为了直接调用getObject方法
https://github.com/mysql/mysql-c ... a15fb47dcaL944-R936
可用连接串直接对fnmsd的研究稍作修改

编辑

将其中5.1.41不可用改成5.1.29以上只有5.1.49不可用,且6.x系列都可以使用
Referencehttps://github.com/fnmsd/MySQL_Fake_Server
https://www.anquanke.com/post/id/203086#h2-1
https://i.blackhat.com/eu-19/Thu ... lization-Attack.pdf

来源:先知(https://xz.aliyun.com/t/11610#toc-0)


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-3-29 05:57 , Processed in 0.014800 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表