鄭 鵬,沙樂天
(南京郵電大學 計算機學院、軟件學院、網絡空間安全學院,南京 210003)
近年來,基于Web 技術的互聯網應用日益增多,序列化方法作為數據傳輸的一種通用方案,涉及用戶信息、數據存儲、訪問控制等諸多方面,一旦受到威脅,可能導致企業無法提供完整服務,用戶信息財產安全無法得到有效保障。XStream 于2021 年爆出若干反序列化漏洞[1-2]。攻擊者需要根據反序列化鏈的傳播特點,在類路徑中搜索節點組裝成反序列化利用鏈,達到裹挾控制流執行危險函數的目的。因此,反序列化利用鏈是反序列化漏洞利用的關鍵,研究如何自動化檢測反序列化利用鏈變得至關重要。
因為反序列化可以讀入任何修改過的文本,為了分析寫入文件的惡意代碼數據是否可以流向危險函數,需要分析數據在系統中是如何傳播和使用的,以確保反序列化過程不會被外界操作篡改。信息流分析技術通過分析程序中數據傳播的合法性以保證信息安全,是防止數據的完整性和保密性被破壞的有效手段。
污點分析[3-4]是一種信息流分析方法,包含靜態分析方法[5]和動態分析方法[6]。傳統的動態分析方法包括fuzz[7-8]、插樁[9-10]等,此類方法程序精確度高,但是只依靠動態方法無法跑出所有可執行程序,因此可靠性不高;靜態分析方法雖然可靠性高,但是消耗系統資源多。為了保持可靠性與時間性能之間的平衡,通常會對數據流分析中的分支進行合并,由此會產生大量的誤報,通常要結合動態分析驗證結果。郭帆等[11]提出了一種針對SQL 注入漏洞的靜態檢測和動態驗證方法。ZUO 等[12]結合靜態和動態方法,基于選擇符號執行實現了App 的惡意URL 檢測工具。
在現有的反序列化鏈分析方法中,KUANG等[13]提出了一種基于抽象語法樹(AST)的漏洞挖掘方法,但這種方法無法完全覆蓋語句編寫方式,難以實現調用關系與數據流動的分析。HAKEN[14]通過字節碼分析實現了反序列化漏洞調用鏈的挖掘工具Gadget Inspector,但其調用鏈不完整。杜笑宇等[15]將Gadget Inspector 中的搜索策略從廣度優先改良成深度優先,挖掘出了一部分新的鏈,但是漏報率高。武永興等[16]通過指針分析反序列化鏈對應的數據類型,但是指針分析會占用過多的計算資源。RASHEED 等[17]提出了一種混合方法,在靜態分析的基礎上使用堆抽象對Java 反序列化漏洞進行模糊測試,但是檢出率低。
本文主要進行以下工作:1)將符號執行與污點分析結合,定義污點傳播規則對傳播路徑進行約束,構建反序列化污點傳播模型;2)采用動靜態結合的方法還原污點傳播路徑,設計并實現字節碼級別的反序列化利用鏈檢測工具Taint Gadget。
污點分析是一種跟蹤分析污點數據在程序之間流動的方法。程序分析將外來的數據當作污點信息來源,然后傳遞給程序內部進行數據監控和流向分析,以此判斷程序中是否存在敏感數據泄露或者危險操作等違反數據完整性和保密性的安全漏洞。LIVSHITS 等[18]將上下文敏感的別名分析與污點分析相結合,靜態檢測Java 程序漏洞。Multi Flow[19]使用了一種基于多源綁定的方法,結合污點分析技術精確判別多組源能否在一次執行中綁定。
污點分析方法可以表示成Source、Sink 和Chain三元組的形式,其中:Source 代表污染源,即引入不受信任或機密的數據到應用程序中的地方,這些數據可能來自API 或者網絡接口;Sink 代表匯聚點,即污點數據聚集的地方,此處會發生違反數據完整性、保密性、可用性的敏感操作;Chain 是傳播的中間節點,Source 經過若干Chain 傳播到Sink。污點分析的研究目標是進行污點傳播路徑分析,即利用程序的依賴信息來分析污點數據在程序中的傳播路徑。利用污點分析可以從源頭跟蹤傳播鏈路。污點分析過程如圖1 所示。

圖1 污點分析過程Fig.1 Process of taint analysis
用反序列化鏈URLDNS 調用鏈舉例說明,探討如何搜索Java 反序列化的調用鏈,如圖2 所示。在URLDNS 調用鏈中,Sink 是Java 內置的java.net.URL類,此類在對URL 對象進行比較時,會觸發一次DNS 解析。通過查看目標URL 的DNS 解析是否被解析,可用于判斷目標站點是否存在反序列化漏洞。此鏈路中Source 是java.util.HashMap。接下來利用反射[20]連接HashMap 和URL。
使用反射可以調用任意對象的任意方法和任意屬性,即可以動態調用對象方法。基于此條件可以構造反序列化漏洞的調用鏈。使用反射將object 變量設置成URL,使HashMap 中的末尾函數hash 可以調用到URL 中的起始函數hashcode,最終調用到危險函數InetAddress.getByName(host)。如何尋找到這樣的調用點并將污點的鏈路進行連接是反序列化污點分析的難點。
符號執行的目的是為了提高程序的效率和覆蓋率,可以用于約束路徑。孫基男等[21]提出一種基于符號執行的注入類漏洞分析方法,輸入符號構造矩陣對注入類漏洞進行檢測和分析。BALZAROTTI等[22]提出一種自動化方法,通過結合靜態檢測、動態檢測和符號執行產生能夠繞過防御的攻擊向量。ALHUZALI 等[23]結合動 態分析 和靜態分析,提 出一種自動生成攻擊向量的污點分析模型。
符號執行的思想是使用符號輸入值取代具體的輸入值,多用于維護路徑。符號執行為每個路徑使用符號狀態進行維護,符號狀態包括路徑約束π和符號狀態σ,分別用于描述路徑的分支條件和存儲變量到符號表達式的映射。符號執行實例如下:
符號執行流程如圖3 所示。程序由3 條執行路徑分化成4 條路經,每條路徑對應一個路徑約束。最終返回true 的有2 條路徑,經求解,路徑約束是size>0;返回false 的也有2 條路徑,路徑約束是size≤0。

圖3 符號執行流程Fig.3 Procedure of symbol execution
通過這個例子可以發現,在使用符號執行分析帶有條件的控制轉移語句時,可以將條件語句轉化為符號取值約束,通過分析約束是否有解判斷路徑的可行性。對于復雜的表達式,路徑求解約束面對復雜的約束需要將符號取值約束求解轉化為可滿足性模理 論(Satisfactory Model Theory,SMT)[24]進 行求解。SMT 是基于SAT[25]的,SAT 是基于布爾邏輯的,但是程序中的條件判斷參數和返回值不全是布爾值,因此,SMT 在SAT 的基礎上增加了對特定類型的條件檢查。
針對當前反序列化漏洞污點分析過程中如何尋找到這樣的調用點并將污點的鏈路進行連接這一難點,本文提出基于符號執行和污點分析的反序列化漏洞檢測模型,整體框架如圖4 所示。該模型包含靜態分析、污點傳播規則定義、污點傳播3 個模塊。靜態分析模塊獲取控制流圖,根據圖數據庫中定義的節點特征獲取Source 和Sink。污點分析模塊基于傳播規則進行傳播,傳播過程中遇到約束時,根據約束規則進行約束求解并動態驗證,不滿足條件則丟棄傳播鏈。
針對污點分析的重要節點,過濾Java 字節碼文件中的無關指令得到敏感Java 字節碼進行方法之間的分析,標記并追蹤Java 虛擬機在執行方法時的棧和局部變量表。在調用方法前對相關參數進行入棧,入棧的數據在本地變量表中能標識一個方法內部的數據流動。在此基礎上,通過遍歷每一個類觀察字節碼文件獲取所有的方法信息和類信息進行類繼承,實現關系的整合分析。通過獲得的信息,分別利用數據流分析、控制流分析和函數調用圖(CG)分析得到參數和返回值之間的關系、每個方法的可達分支、方法與子方法之間的參數對應關系,最終生成控制流圖(CFG)。
函數調用圖記錄的是同一個類中的函數傳播。擴展的調用圖(ECG)包括跨過程的隱式流函數方法調用。在函數調用圖的基礎上,從已有的方法中匹配調用方法名和被調用方法名,生成新的調用關系以此全面地記錄方法調用。最終的整合圖如圖5所示。

圖5 擴展的調用圖Fig.5 Extended call graph
從靜態分析中已提取出全部的數據流、控制流、調用圖。從已有的3 個圖中提取頂點,包括Source、Sink 和Chain。根據CFG 衍生出的ECG 得到圖傳播的邊,符號執行提取的是一個Chain 到另一個Chain之間函數傳播(Function fi)的約束,約束的邊求解之后調用的下一個符合條件的Chain。使用符號執行確定ECG 傳播過程中的約束規則。
2.2.1 污點節點定義
對于反序列化漏洞,一般是從重寫了readObject等方法的Source 開始進行相關的調用,經過傳播節點Chain,最終調用到一些可能造成危害的Sink,如執行命令、文件寫入等。Source 節點和Chain 節點都必須聲明序列化接口。
常出現的讀取對象函數和代理函數的入口歸類為Source,一般是重寫讀入函數。Sink 函數的特征是調用命令的函數,常見的Source和Sink 如表1 所示。

表1 常見的Source 和SinkTable 1 Common Sources and Sinks
2.2.2 污點傳播約束
根據已有的數據流和調用圖標記,獲取所有的數據流關系和方法調用關系。依據過程間調用規則生成類圖邊,邊滿足條件語句的約束和過程間傳播流的約束。對于路徑中的每個方法,從ECG 中獲取敏感變量,用符號執行追蹤傳播鏈。
約束傳播需要提取路徑的約束信息,以生成符合約束的對象,信息包括對象敏感信息和成員變量敏感信息,對象敏感信息為污點分析的調用函數建立正確的類,變量敏感信息為類方法中函數調用建立正確的參數。
函數調用傳播類型分為過程間顯示流調用和過程間隱式流調用,分別在Chain 內傳播和Chain 之間傳播,即類中調用是顯示流調用,類間調用是隱式流調用。過程間隱式流調用根據過程間的變量位置又分為2 種類型,分別是針對成員變量的直接賦值的調用和針對函數中變量的非直接賦值的調用。針對成員變量和參數的過程間傳播可以直接賦值,然而針對函數中變量非直接賦值的調用,不僅要滿足變量能夠遞歸傳播到調用點的條件,還必須滿足調用的對象擁有賦值函數的條件。
ECG 傳播流程如下:
其中:→代表顯示流傳播;->代表隱式流傳播。
擴展函數調用圖傳播流程如圖6 所示,其中,var1、var2 是A 類成員變量,可以直接賦值。根據“(Function)getterObj”表達式分析繼承關系,首先根據要實現Function 接口和序列化接口的條件找到GetterObj 類,然后發 現GeterObj 不 是A 類的成員變量,不能直接賦值,需要滿足GetterObj 類存在賦值函數的條件,最后發現Getterobj 是GetterSlot 類中的成員變量,GetterSlot 中存在賦值函數,因此可以直接賦值。

圖6 擴展函數調用圖傳播流程Fig.6 Propagation procedure of extended function call graph
根據以上分析給出以下約束規則:
規則1污點傳播遞歸尋找隱式流傳播中object變量可賦值的類。
規則2對類型限制的條件語句和對成員變量的值限制的條件語句進行污點追蹤和約束。
為了防止約束求解中的路徑爆炸,只對類型限制的條件語句和對成員變量的值限制的條件語句進行污點追蹤和約束,根據規則生成約束的圖邊,記錄所有成員變量的過程間傳播條件,到達類間傳播的調用點時求解路徑約束,不滿足約束則無法傳播。
過程間污點傳播過程如算法1 所示,構造反序列化鏈從Source 點方法出發,尋找該方法能污染的方法,并從本類的方法中繼續尋找下一個能污染的Chain,直到遇到Sink點,污點以邊的形式傳播構成圖,圖的邊根據調用規則生成,邊的約束根據規則生成。將污染傳遞規則作為邊限制的依據,傳遞到有效的條件語句時會根據規則對需要遞歸遍歷的過程間對象變量進行二次檢查,通過數據流檢查是否可以賦值。
算法1過程間污點傳播算法
動態驗證階段使用靜態分析的輸出,通過反射重構具有與靜態分析中相同屬性的對象。從Chain 的調用點得到另一個調用點。從靜態分析路徑構造對象的過程如算法2 所示,該過程以路徑作為輸入,從路徑末尾開始反方向遍歷路徑實例化對象,每傳播到一個對象,為前一個變量實例化一個對象并將當前對象加載到該字段,然后繼續遍歷路徑的其余對象將其實例化,最后從Source 開始遍歷,使用動態污點分析監測能否調用到Sink,為Sink設置若干惡意類。
算法 2對象構造算法
算法實現流程如圖7 所示,其中包括污點標記模塊、污點傳播與驗證模塊。

圖7 算法實現流程Fig.7 Implementation procedure of the algorithm
步驟1污點標記
使用ASM 讀取全部類和方法的信息,解析類的繼承關系、方法繼承關系、構造類和方法的映射、條件語句等相關信息。模擬本地變量表和操作數棧的變化進行污點傳播的追蹤,分為以下2 段:
第1 段是方法內的污點傳播,追蹤方法的入參能否影響到方法返回結果。通過在模擬的棧頂數據打標記,并在訪問字段和方法時根據規則判斷是否能夠傳播污點,對棧頂數據使用打標記或清空的方式來進行污點傳播的分析。直到方法return 時,取出棧頂返回數據的污點標記。
第2 段是方法間的污點傳播,同樣采用模擬本地變量表和操作數棧的變化進行污點追蹤,使用調用者的入參索引作為污點,傳播至被調用者入參時,其可以影響到下一個方法的調用。
將上述信息保存為類圖數據,保存到Neo4j 數據庫中。
步驟2污點傳播與驗證
依據傳播規則構造反序列化鏈從Source 點方法出發,根據污點傳播規則利用深度遍歷傳播,深度遍歷過程中判斷隱式流的傳播對象是否可控。在傳播過程中遇到約束時使用驗證模塊,直到遇到Sink 點。類圖1->類圖2->類圖3 正向遍歷直到類圖N。類圖之間的約束根據以下約束獲得:
約束1根據數據流分析。判斷隱式流的調用對象與其參數是否可以寫入。若不能直接寫入,根據數據流的參數流動遞歸,直到找到可以直接賦值函數的可疑類為止。接下來從數據流中提取出可疑類及其參數的父子樹,通過數據流分析得到繼承關系,分析父子關系是否成立,其中包含對接口的判斷。
約束2抓取顯示流信息。針對過程間傳播變量提取當前Chain 中的類型判斷指令的字節碼,使用Z3 斷言對約束1 中生成的可疑過程間對象判斷。
將上述約束中的變量表示為Z3-API[26]可以求解的內容,將object 表示為object.tostring,其成員變量表示為object.attribute.toString。結合約束1 和約束2從Neo4j數據庫中獲取ECG 信息,求得下一個調用的類。Z3 求解采用惰性計算的形式,符號表達式約束在顯示流傳播中關聯并保存,在隱式流傳播時計算求解。完整的傳播完成后根據符號執行的路徑記錄還原object 源,動態分析驗證路徑的有效性。根據約束信息返回對象和成員變量的值,反射構造反序列化類驗證從Source 到Sink 的路徑,生成測試用例。
本文選 用1 臺基于Inter?CoreTMi7-6700H CPU 2.6 GHz 處理器、內存8 GB 的主機進行實驗,系統為64 位Windows 10。
實驗選取12 個包含反序列化漏洞的Java 庫,如表2 所示,其中包括commons-collections-3.2.1、commonscollections-4.0、rome-1.0、groovy-2.3.9 等。實驗工 具包括Taint Gadget、Gadget Inspector 以 及Threedr3am 改進后的T-Gadget Inspector,將3 種工具對于這12 種Java 庫的反序列化漏洞調用鏈的檢測情況進行對比,檢查反序列化漏洞調用鏈是否有效。

表2 ysoserial 數據集Table 2 ysoserial dataset 單位:個
對3 個工具檢測的命中率、漏報率和時間進行統計,如表3 所示,可以看出:Taint Gadget 對于反序列化漏洞調用鏈的檢測命中率平均為70.6%,效果優于T-Gadget Inspector 的23.2% 和Gadget Inspector 的8.5%;Taint Gadget 對于反序列化漏洞調用鏈的挖掘漏報率平均約為4.1%,效果優于T-Gadget Inspector 的35% 和Gadget Inspector 的80.2%;Taint Gadget 對于不同Java 基礎庫的測試時間較為穩定,其平均時間約為 78.4 s,T-Gadget Inspector 平均時 間約為74.3 s,Gadget Inspector 的平均時間約為68.7 s。Taint Gadget 消耗更多時間的原因是路徑約束消耗了更多的系統資源。

表3 靜態數據對比Table 3 Static data comparison
根據符號執行的求解結果,利用動態方法構造到達Sink 的對象組成路徑,與基于動靜態結合的工具Serhybrid 動態運行對比結果如表4 所示,其中,TPs 代表路徑存在的數量,FPs 代表失敗路徑不存在的數量??梢钥闯觯篢aint Gadget 的動態檢出率平均為90.6%;Serhybridpub 的動態檢出率平均28.5%;Taint Gadget 的動態運行時間約為20.8 s;Serhybridpub 的平均動態運行時間約為1 734.3 s。因為Serhybridpub 采用的是非定向的模糊測試方法,所以時間消耗遠超本文的方法,可見本文的動態檢出率和時間性能都好于Serhybridpub。cc3 中有2 條鏈沒有檢測出的原因是沒有對動態代理做出約束,groovy 和mozilia 各有一條鏈沒有檢測出的原因是路徑的條件過于苛刻。

表4 動態數據對比Table 4 Dynamic data comparison
下面將介紹針對commons-collections4-4.0.jar 包中CC2 反序列化漏洞鏈路進行復現的過程。首先從靜態分析的Source 結果中搜索到有入口函數readObject 的類PriorityQueue,然后通過污點分析找到類間傳播的調用點,記錄類中傳播的條件語句,到調用點根據約束生成對象,對象的成員變量滿足路徑條件。約束求解過程如圖8 所示。

圖8 污點分析過程Fig.8 Process of taint analysis
根據路徑條件約束,comparator.compare 是第1 個隱式 傳播調用點,tansformer.transform 是 第2 個隱式傳播調用點,method.invoke 是危險函數調用點,可以調用惡意類TeamplatesImpl。記錄類中的路徑約束直到調用點求解,分別得到PriorityQueue、TransformingComparator、InvokerTransformer 的過程間傳播約束條件,對約束求解構造3 個對象。
Priority Queue 的初始化需要反射設置成員變量size、comparator、queue。根據約束求解的結果構造對 象Priority Queue,將size 反射設置為2,將comparator 反射設置為Transforming Comparator,Tranfroming Comparator 初始化需要將成員變量comparator 設置為InvokerTransforming,反射創 建Invoker Transformer 對象,最終成功地將Priority Queue 構造為 Priority Queue
Taint Gadget的靜態檢測過程與T-Gadget Inspector和Gadget Inspector 的區別體現在靜態分析、約束條件、污點傳播等3 個方面。首先,Taint Gadget 靜態分析過程中比其他2 種工具多收集了成員變量信息,方法中的參數傳播信息和條件語句,為約束條件和污點傳播提供條件;其次,Taint Gadget 基于條件語句生成約束條件,其他2 種工具沒有記錄條件語句,導致沒有生成約束,所以會記錄不存在的鏈路,造成路徑爆炸,導致命中率較低;最后,Taint Gadget 污點傳播采用過程間傳播分析,準確記錄了類的變量傳播,提高了傳播精度。其他2 種工具采用的是過程內傳播分析方法,只記錄函數的調用關系,不記錄調用關系所需的變量信息是否符合調用要求,丟失污點傳播精度,導致誤報率較高。
Taint Gadget 的動態驗證過程與Serhybridpub 的區別是Serhybridpub 使用Randoop 進行模糊測試,通過隨機輸入變量值測試到達危險函數,因此對類成員變量信息不敏感。如圖8 所示,Taint Gadget 在靜態的約束求解過程中已經求解出構造對象所需的類成員變量信息。相對而言,Taint Gadget 對類成員信息敏感,針對性更強,省去了模糊測試的時間。
本文利用符號執行和污點分析技術,針對污點傳播描述規則提出約束,實現Java 反序列化漏洞檢測和驗證工具Taint Gadget。實驗結果表明,Taint Gadget 在命中率和漏報率方面性能優于現有的工具Gadget Inspector 和T-Gadget Inspector,且沒有消耗太多的時間,Taint Gadget 的動態性能也好于Serhybridpub。本文沒有構建動態代理約束,下一步工作將針對動態代理特性進行約束構建和求解。