前言css
關於java反序列化漏洞的原理分析,基本都是在分析使用Apache Commons Collections這個庫,形成的反序列化問題。然而,在下載老外的ysoserial工具並仔細看看後,我發現了許多值得學習的知識。html
至少能學到以下內容:java
不一樣反序列化payload玩法靈活運用了反射機制和動態代理機制構造POCgit
java反序列化不只是有Apache Commons Collections這樣一種玩法。還有以下payload玩法:github
CommonsBeanutilsCollectionsLogging1所需第三方庫文件: commons-beanutils:1.9.2,commons-collections:3.1,commons-logging:1.2 CommonsCollections1所需第三方庫文件: commons-collections:3.1 CommonsCollections2所需第三方庫文件: commons-collections4:4.0 CommonsCollections3所需第三方庫文件: commons-collections:3.1(CommonsCollections1的變種) CommonsCollections4所需第三方庫文件: commons-collections4:4.0(CommonsCollections2的變種) Groovy1所需第三方庫文件: org.codehaus.groovy:groovy:2.3.9 Jdk7u21所需第三方庫文件: 只需JRE版本 <= 1.7u21 Spring1所需第三方庫文件: spring框架所含spring-core:4.1.4.RELEASE,spring-beans:4.1.4.RELEASEweb
上面標註了payload使用狀況下所依賴的包,諸位能夠在源碼中看到,根據實際狀況選擇。spring
經過對該攻擊代碼的分析,能夠學習java的一些有意思的知識。並且,裏面寫的java代碼也很值得學習,巧妙運用了反射機制去解決問題。老外寫的POC仍是很精妙的。shell
準備工做apache
在github上下載ysoserial工具。使用maven進行編譯成Eclipse項目文件,mvn eclipse:eclipse。要你聯網下載依賴包,請耐心等待。若是卡住了,中止後再次執行該命令。api
導入後,能夠看到裏面有8個payload。其中ObjectPayload是定義的接口,全部的Payload須要實現這個接口的getObject方法。下面就開始對這些payload進行簡要的分析。
payload分析
1. CommonsBeanutilsCollectionsLogging1
該payload的要求依賴包挺多的,可能碰到的狀況不會太多,但用到的技術是極好的。對這個payload執行的分析,請閱讀參考資源第一個的分析文章。
先直接看代碼:
第一行代碼final TemplatesImpl templates = Gadgets.createTemplatesImpl(command);建立了TemplatesImpl類的對象,裏面封裝了咱們須要的命令執行代碼。並且是使用字節碼的形式存儲在對象屬性中。
下面就具體分析下這個對象的產生過程。
(1) 利用TemplatesImpl類存儲危險的字節碼
在產生字節碼時,用到了JDK中javassist類。具體瞭解能夠參考這篇博客http://www.cnblogs.com/hucn/p/3636912.html。
下面是我編寫的一個簡單的樣例程序,便於理解:
上述代碼首先獲取到class定義的容器ClassPool,並找到了我自定義的Point類,由今生成了cc對象。這樣就能夠開始對類進行修改的任意操做了。並且這個操做是直接寫字節碼。這樣能夠繞過許多安全機制,正像工具中註釋說的:
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
後面的操做即是利用我自定義的模板類Point,生成新的類名,並使用insertAfter方法插入了惡意java代碼,執行命令。有興趣的能夠再詳細瞭解這個類的用法。這裏再也不贅述。
這段代碼運行後,會在當前目錄生成字節碼(class文件)。使用java反編譯器可看到源碼,在原始模板類中插入了惡意靜態代碼,並且以字節碼的形式直接存儲。命令行直接運行,能夠執行彈出計算器的命令:
如今看看老外工具中,生成字節碼的代碼爲:
根據以上樣例分析,能夠清楚看見:前面幾行代碼,即生成了咱們須要的插入了惡意java代碼的字節碼數據。該字節碼其實能夠看作是一個類(.class)文件。final byte[] classBytes = clazz.toBytecode();將其轉成了二進制數據進行存儲。
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes,ClassFiles.classAsBytes(Foo.class)});這裏又來到了一個有趣知識,那就是java反射機制的強大。ysoserial工具封裝了使用反射機制對對象的一些操做,能夠直接借鑑。
具體能夠看看其源碼,這裏在工具中常用的Reflections.setFieldValue(final Object obj, final String fieldName, final Object value);方法,即是使用反射機制,將obj對象的fieldName屬性賦值爲value。反射機制的強大之處在於:
能夠動態對對象的私有屬性進行改變賦值,即:private修飾的屬性。動態生成任意類對象。
因而,咱們便將com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl類生成的對象templates中的_bytecodes屬性,_name屬性,_tfactory屬性賦值成咱們但願的值。
重點在於_bytecodes屬性,裏面存儲了咱們的惡意java代碼。如今的問題即是:如何觸發加載咱們的惡意java字節碼?
(2) 觸發TemplatesImpl類加載_bytecodes屬性中的字節碼
在TemplatesImpl類中存在執行鏈:
這在ysoserial工具中的註釋中是能夠看到的。在源碼中,咱們從TemplatesImpl.getOutputProperties()開始跟蹤,不難發現上面的執行鏈。最終會在getTransletInstance方法中看到以下觸發加載自定義ja字節碼部分的代碼:
#!javaprivate Translet getTransletInstance()throws TransformerConfigurationException { ............. if (_class == null) defineTransletClasses();//經過ClassLoader加載字節碼,存儲在_class數組中。 // The translet needs to keep a reference to all its auxiliary // class to prevent the GC from collecting them AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();//新建實例,觸發惡意代碼。 ............
在defineTransletClasses()方法中,會加載咱們以前存儲在_bytecodes屬性中的字節碼(能夠看作類文件),進而返回類的Class對象,存儲在_class數組中。下面是調試時候的截圖:
能夠看到在defineTransletClasses()後,獲得類的Class對象。而後會執行newInstance()操做,新建一個實例,這樣便觸發了咱們插入的靜態惡意java代碼。若是接着單步執行,便會彈出計算器。
經過以上分析,能夠看到:
只要可以自動觸發TemplatesImpl.getOutputProperties()方法執行,咱們就能達到目的了。 (3) 利用BeanComparator比較器觸發執行
咱們接着看payload的代碼:
很簡單,將PriorityQueue(優先級隊列)插入兩個元素,並且須要一個實現了Comparator接口的比較器,對元素進行比較,並對元素進行排隊處理。具體能夠看看PriorityQueue類的readObject()方法。
從對象反序列化過程原理,能夠知道會首先調用該對象readObject()。固然在序列化過程當中會首先調用該對象的writeObject()方法。這兩個方法能夠對比着看,方便理解。
首先,在序列化PriorityQueue類實例時,會依次讀取隊列中的對象,並放到數組中進行存儲。queue[i] = s.readObject();而後,進行排序操做heapify();。最終會到達這裏,調用比較器的compare()方法,對元素間進行比較。
這裏傳進去的,即是BeanComparator比較器:位於commons-beanutils包。
因而,看看比較器的compare方法。
o1,o2即是要比較的兩個對象,property即咱們須要比較對象中的屬性(可控)。一開始property賦值爲lowestSetBit,後來改爲真正須要的outputProperties屬性。
PropertyUtils.getProperty( o1, property )顧名思義,即是取出o1對象中property屬性的值。而實際上會去調用o1.getProperty()方法獲得property屬性值。
到這裏,能夠畫上完美的一個圈了。咱們只需將前面構造好的TemplatesImpl對象添加到PriorityQueue(優先級隊列)中,而後設置比較器爲BeanComparator("outputProperties")便可。
那麼,在反序列化過程當中,會自動調用TemplatesImpl.getOutputProperties()方法。執行命令了。
我的總結觀點:
只須要想辦法:自動調用TemplatesImpl的getOutputProperties方法。或者TemplatesImpl.newTransformer()即能自動加載字節碼,觸發惡意代碼。這也在其餘payload中常常用到。 觸發原理:提供會自動調用比較器的容器。如:將PriorityQueue換成TreeSet容器,也是能夠的。
爲了在生成payload時,可以正常運行。在代碼中,先象徵性地加入了兩個BigInteger對象。
後面使用反射機制,將comparator中的屬性和queue容器存儲的對象都改爲咱們須要的屬性和對象。
不然,在生成payload時,便會彈出計算器,拋出異常,沒法正常執行了。測試以下:
2. Jdk7u21
該payload實際上是JAVA SE的一個漏洞,ysoserial工具註釋中有連接:https://gist.github.com/frohoff/24af7913611f8406eaf3。該payload不須要使用任何第三方庫文件,只需官方提供的JDK便可,這個很方便啊。 不知Jdk7u21之後怎麼補的,先來看看它的實現。
在介紹完上面這個payload後,再來看這個能夠發現:CommonsBeanutilsCollectionsLogging1借鑑了Jdk7u21的利用方法。
一樣,Jdk7u21開始便建立了一個存儲了惡意java字節碼數據的TemplatesImpl類對象。接下來就是怎麼觸發的問題了:如何自動觸發TemplatesImpl的getOutputProperties方法。
這裏首先就有一個有趣的hash碰撞問題了。
(1) "f5a5a608"的hash值爲0
類的hashCode方法是返回一個獨一無二的hash值(int型),去表明這個惟一對象。若是類沒有重寫hashCode方法,會調用原始Object類中的hashCode方法返回一個hash值。
String類的hashCode方法是這麼實現的。
因而,就有了有趣的值:
能夠看到"f5a5a608"字符串,經過hashCode方法生成的hash值爲0。這在以後的觸發過程當中會用到。
(2) 利用動態代理機制觸發執行
Jdk7u21中使用了HashSet容器進行觸發。添加了兩個對象,一個是存儲了惡意java字節碼數據的TemplatesImpl類對象templates,一個是代理了Templates接口的proxy對象,使用了動態代理機制。
以下是Jdk7u21生成payload時的主要代碼:
HashSet容器,就能夠當作是一個HashMap<key,new>,key即是咱們存儲進去的數據,對應的value都只是靜態的Object對象。
一樣,來看看HashSet容器中的readObject方法。