1
Java序列化與反序列化
序列化與反序列化對於Java程式師來說,應該不算陌生了,序列化與反序列化簡單來說就是Java對象與數據之間的相互轉化。那麼對於完全面向對像的Java語言來說為什麼要有序列化機制?實質上,序列化機制並不只局限於Java語言,序列化的本質是記憶體對象到資料流程的一種轉換,我們知道記憶體中的東西不具備持久性,但有些場景卻需要將對象持久化保存或傳輸。例如緩存系統中存儲了用戶的Session,如果緩存系統直接下線,帶系統重啟後用戶就需要重新登陸,為了使緩存系統記憶體中的Session對象一直有效,就需要有一種機制將對象從記憶體中保存入磁片,並且待系統重啟後還能將Session對象恢復到記憶體中,這個過程就是對象序列化與反序列化的過程,從而避免了用戶會話的有效性受系統故障的影響。此外,在Java工程中,序列化還廣泛應用於JMX,RMI,網絡傳輸(協定包對象)等場景,可以說序列化機制賦予了記憶體對象持久化的機會,就像虛擬機器鏡像(VMware Take a snapshot),也可以將序列化機制看作是記憶體對象的一種鏡像機制。
在Java中,只要一個類實現了java.io.Serializable介面,那麼它就可以通過ObjectInputStream與ObejctOutputStream序列化,如下我們類比了Session對象持久化存儲與從磁片加載的過程:
結合注釋,這段測試程式碼應該不難理解,我們可以看到Java對象序列化就依賴於ObejctOutputStream的writeObject方法,而反序列化是由ObjectInputStream的readObject方法實現的,下圖是作者畫的一個序列化示意圖:
2
反序列化漏洞成因
2015年年底,由公共依賴庫Apache Common Collections引起的Java任意命令執行漏洞的嚴重安全問題,使得Java反序列化漏洞逐漸進入了安全
研究人員的視野(在此之前存在,但並未被重視),而任意命令執行的成因正是前文反序列化操作的ObejctInputStream類的readObject方法觸發的。
Java序列化機制雖然有默認序列化機制,但也支持用戶自定義的序列化與反序列化策略。例如對象的一些成員變數沒必要序列化保存或傳輸,就可以不序列化,或者也可以對一些敏感欄位進行處理等自定義對象序列化的行為,而自定義序列化規則的管道就是重寫writeObejct與readObject。當對象重寫了writeObejct或readObject方法時,Java序列化與反序列化就會調用用戶自定義的邏輯了,下圖示例我們對Session對象重寫了序列化處理函數:
OK,到目前為止一切都在程式師的掌控之中!何來的漏洞之說?呵呵,意外往往就發生在不經意之間,如果反序列化過程中提供了命令執行的機會,那麼任意命令執行漏洞就產生了,如下我們在Session對象的readObject函數中新增了執行命令的程式碼:
此時,駭客只需要將Session對象的sessionId構造成想要執行的命令字串,即可實現遠程命令執行的功能,如下成功打開系統calc.exe行程:
好了,我們再來梳理一下上例中漏洞存在的條件與利用思路:
- 條件:首先Session對象重寫了反序列化函數readObject,並且readObject方法存在執行命令的機會。
條件:首先Session對象重寫了反序列化函數readObject,並且readObject方法存在執行命令的機會。
- 漏洞利用:正常的反序列化流程會重新生成一個正常Session對象,而惡意的序列化數據抓住了反序列化的漏洞執行命令的機會,精心構造了序列化對象,使得資料流程反序列化的過程中惡意命令得以執行。
漏洞利用:正常的反序列化流程會重新生成一個正常Session對象,而惡意的序列化數據抓住了反序列化的漏洞執行命令的機會,精心構造了序列化對象,使得資料流程反序列化的過程中惡意命令得以執行。
看到這裡,作為程式師的你肯定哈哈大笑!對象的反序列化函數誰會這樣寫?當然本示例只是為了以最直觀的管道演示反序列漏洞產生原因,就直接提供了一個HelloWorld級別的漏洞示例,實際上,近兩年 Java Apache-CommonsCollections造成的序列化漏洞與Spring框架的反序列化漏洞(spring-tx.jar)的成因與原理都與上例相似,只是漏洞利用的構成比較複雜而已。
3
Apache-CommonCollections REC漏洞解析
該漏洞曝光於2015年年底,被譽為當年“最被低估了的漏洞”,利用思路一經爆出,各大Java web廠商紛紛躺槍,受此影響的Web服務器有:WebLogic、WebSphere、JBoss、Jenkins、OpenNMS等。介於該漏洞曝光距今已經有兩年之久,並且網上也有對此分析的很透徹的文章,本文就來講一下這個可以通過精心構造觸發命令執行的漏洞的要點。
該漏洞的主要問題就出現在org.apache.commons.collections.Transformer介面上,在Apache-CommonsCollections包中,有一個InvokerTransformer類實現了Transformer介面,並且InvokerTransformer這個類也很恰巧,InvokerTransformer的transform方法提供了一個可以通過Java反射機制,調用任意Java方法的機會:
相信很多漏洞利用者對invoke非常敏感,意味著提供了方法調用的機會,再結合方法名與參數都能通過InvokerTransformer搆造函數來控制,囙此該類一定是漏洞挖掘者反復徘徊的點。那該如何利用?在commons-collections包中,InvokerTransformer結合ChainedTransformer就能構造一個Java類加載函數調用鏈,POC的作者是如下構造的:
這樣構造的原因在於ChainedTransformer的transform函數會依次調用數組中InvokerTransformer的transform函數,構成一個函數調用鏈,下圖是調試狀態下,ChainedTransformer的transform方法的變數狀態:
可以看到,通過ChainedTransformer.transform的構造相當於反射調用了Runtime.getRuntime().exec(),從而觸發了命令的執行。OK,那麼問題來了,誰去觸發調用ChainedTransformer.transform函數呢?漏洞利用者找到了包中TransformedMap.checkSetValue()方法:
這樣一來,POC就可以通過構造一個TransformedMap對象,然後再想辦法觸發checkSetValue函數即可,而TransformedMap在Apache-CommonsCollections這個集合庫中可以通過TransformedMap.decorate修飾器方法來修飾一個Map對象,而Map對象在反序列化是只需調用MapEntiry.setValue就能觸發checkSetValue函數,進而進一步觸發第一步構造的ChainedTransformer.transform方法,從而實現漏洞利用的目的。
以上就是Apache-CommonsCollections RCE漏洞的利用思路,該漏洞的實質是Apache-CommonsCollections為利用者提供了一系列可以構造行命令機會。但是聰明的你一定會問,上面的漏洞觸發條件並不是在反序列化函數readObject中實現的,怎麼能在反序列化中觸發POC的執行?好問題!思路是這樣的,可以找到一個這樣的對象:
- 該類自定義重寫了readObejct反序列化函數。
該類自定義重寫了readObejct反序列化函數。
- readObecjt方法中調用了Map的checkSetValue函數,並且該Map對象還可以通過搆造函數構造。
readObecjt方法中調用了Map的checkSetValue函數,並且該Map對象還可以通過搆造函數構造。
呵呵,我知道,你又開始笑了!的確利用條件比較苛刻,也難怪該漏洞在2015年初沒有被重視起來,但是,無巧不成書,老外還就給你在JDK中找到了一個滿足條件的的對象:sun.reflect.annotation.AnnotationInvocationHandler。好了,廢話不多說,來看一下的AnnotationInvocationHandler的反序列化函數滿不滿足觸發要求:
因為setValue函數會調用checkSetValue:
從而在sun.reflect.annotation.AnnotationInvocationHandler對象反序列化時,就滿足了ChainedTransformer、TransformerMap構造的POC觸發的條件,最後我們給出完整的構造Apache CommonCollections反序列化漏洞利用POC的程式碼(POC來自網絡):
小夥伴們看到這裡可能一頭霧水了,作者這裡借用鬥象科技在分析該漏洞時畫的一幅漏洞POC構造與觸發流程圖,小夥伴們可以借此再來梳理一下該漏洞利用的思路:
囙此,POC的觸發流程為:TransformedMap->AnnotationInvocationHandler.readObject()->setValue()->checkSetValue()最後由反序列化readObject時觸發執行。的確,該漏洞的觸發方法與構造思路非常精妙,就不得不佩服這位構造出POC的老外。
4
漏洞如何防範?
首先,開發者要有安全意識,應該清楚瞭解項目使用到的組件,以及這些組件是否存在漏洞,雖然說Apache-CommonsCollections RCE漏洞曝光將近兩年時間,但使用3.2.2版本之前的Apache-CommonsCollections的web服務框架依然存在,近期曝光的Apache James 3.0.0版本的CVE-2017-12628漏洞就是由於還在使用commons-collections-3.2.1.jar造成的。不過,安全意識對於很多開發者不是沒有,而是沒有接觸過,作者曾在一個使用struts框架的團隊待過,他們寫出的程式碼真的是Web漏洞一籮筐啊。
此外,也可以禁用JVM執行外部命令(Runtime.exec),因為Runtime.exec對於大多數Java正常應用來說是不會用到的,但是確是駭客控制Web服務後運行命令的重要方法,囙此該手段是JavaWeb防護常用的且有效的手段,如果從攻擊者角度看這種防護效果,那就是攻擊工具webshell只能檔案相關操作,無法執行命令。可以通過擴展SecurityManager來禁用Runtime.exec,當觸發運行時還可加入報警邏輯,啟動應急回應:
5
反序列化漏洞利用的其他思考
作者認為反序列化漏洞的利用應該更為廣泛,思路不應該僅僅局限於遠程命令執行漏洞的利用,也存在著系統數據篡改污染的危險,造成系統業務安全問題。例如序列化對象在系統中承擔了帳單、金額、認證、鑒權等職責,如果可以被反序列化惡意利用,後果也非常嚴重。這樣的威脅不亞於命令執行的威脅,並且事後難於排查,因為是記憶體攻擊,載荷不落地。所以,反序列化對於業務安全的威脅也是我們一個值得深思的問題。
對了,有興趣的從程式碼層面研究一把的小夥伴可以去公眾號配套的程式碼倉庫去下載。