作者:[email protected]雲影實驗室
0X00前言
最近幾天國外安全研究員Soroush Dalili(@irsdl)公佈了.NETRemoting應用程序可能存在反序列化安全風險,當服務端使用HTTP通道中的SoapServerFormatterSinkProvider類作為通道接收器並且將自動反序列化TypeFilterLevel内容設定為Full的時候會造成反序列化漏洞,從而實現遠程RCE攻擊,本文筆者從原理和程式碼稽核的視角做了相關介紹和複現,並且歸納成.NET反序列化漏洞系列課程中的第五課。
0X01 .NET Remoting概念
.NET Remoting是一種分佈式應用解決方案,它允許不同AppDomain(應用程序域)之間進行通信,這裡的通信可以是在同一個行程中進行或者一個系統中的不同行程間進行的通信。.NET Remoting框架也提供了多種服務,包括啟動和生存期支持,以及負責與遠程應用程序進行消息傳輸的通道。應用程序可在重視效能的場景下使用二進位資料傳輸,在需要與其他遠程處理框架進行互動的場景下使用XML資料傳輸。在從一個AppDomain向另一個AppDomain傳輸消息時,所有的XML數據都使用SOAP協定,總體看.NET Remoting有以下三點優勢:
- 提供了一種允許對象通過AppDomain與另一對象進行互動的框架(在Windows作業系統中,是將應用程序分離為單獨的行程。這個行程形成了應用程序程式碼和數據周圍的一道邊界。如果不採用行程間通信(RPC)機制,則在一個行程中執行的程式碼就不能訪問另一行程。這是作業系統對應用程序的保護機制。然而在某些情况下,我們需要跨過應用程序域,與另外的應用程序域進行通信,即穿越邊界。)
- 以服務的管道來發佈服務器對象(程式碼可以運行在服務器上,然後用戶端再通過Remoting連接服務器,獲得該服務物件並通過序列化在用戶端運行。)
- 用戶端和伺服器端有關對象的鬆散耦合(在Remoting中,對於要傳遞的對象,設計者除了需要瞭解通道的類型和埠號之外,無需再瞭解數据包的格式。這既保證了用戶端和伺服器端有關對象的鬆散耦合,同時也優化了通信的效能。)
0X02 .NET Remoting通道和協定
通道是Server和Client進行通信用的,在.NET Remoting中提供了三種通道類型,
- IpcChannel:位於命名空間System.Runtime.Remoting.Channels.Ipc下,提供使用IPC協定傳輸消息的通道實現。
- TcpChannel:位於命名空間System.Runtime.Remoting.Channels.Tcp下,提供使用TCP協議傳輸消息的通道實現。
- HttpChannel:位於命名空間System.Runtime.Remoting.Channels.Http下,為遠程調用實現使用HTTP協議傳輸消息的通道。
IpcChannel提供了使用Windows行程間通信(IPC)系統在同一電腦上的應用程序域之間傳輸消息的機制。在同一電腦上的應用程序域之間進行通信時,IPC通道比TCP或HTTP通道要快得多。但是IPC只在本機應用之間通信。所以,在用戶端和服務端在同一台機器時,我們可以通過注册IpcChannel來提高Remoting的效能。但如果用戶端和服務端不在同一台機器時,我們不能注册IPCChannel,在此不多介紹。
TcpChannel提供了基於Socket的傳輸工具,使用Tcp協定來跨越Remoting邊界傳輸序列化的消息流。默認使用二進位格式序列化消息對象,具有更高的傳輸效能,適用於局域網。
HttpChannel提供了一種使用Http協定,使其能在Internet上穿透防火牆傳輸序列化消息流,HttpChannel類型使用Soap格式序列化消息對象,囙此它具有更好的互操作性。適用於廣域網,如圖
0x03攻擊原理
研究漏洞之前先普及下HttpChannel的相關基礎知識,HttpChannel類使用SOAP協定在遠程對象之間傳輸消息,並且符合SOAP1.1的標準,所有的消息都是通過SoapFormatter傳遞,此格式化器會將消息轉換為XML數據並進行序列化,同時向資料流程中添加所需的SOAP標頭。如果指定了二進位格式化程式,則會創建二進位資料流程。隨後,將使用HTTP協議將資料流程傳輸至目標URI。HttpChannel分類如圖
下麵是從微軟檔案裏摘取定義服務端的程式碼片段:
每行程式碼分別實現了創建服務端通道並且綁定本地埠9090;注册服務端通道;以及通過訪問URI為RemoteObject.rem的地址調用遠程的對象,在.NETRemoting中有個啟動管道的概念,表示在訪問遠程類型的一個對象實例之前,必須通過一個名為Activation的行程創建它並進行初始化。程式碼中引入了服務端啟動的WellKnown管道,看下圖
WellKnown理解為知名對象的啟動,服務器應用程序在啟動對象實例之前會在統一資源識別字(URI)上來發佈這個類型。然後該服務器行程會為此類型配寘一個WellKnown對象,並根據指定的埠或地址來發佈對象,它的啟動分為SingleTon模式、SingleCall模式,SingleTon類所代表的類型規定每個AppDomain只能存在一個實例,當SingleTon類型加載到AppDomain的時候,CLR調用它的靜態構造器去構造一個SingleTon對象,並將它的引用保存到靜態欄位中,而且該類也沒有提供任何的公共構造器方法,這就防止了其他任何程式碼構造該類的其他實例。具體到這兩種模式各有區別,都可以觸發漏洞,因不是重點所以不做過多介紹。
3.1、遠程對象
圖中的RemoteObject類,這是一個遠程對象,看下微軟官方的定義
RemoteObject繼承自MarshalByRefObject類,MarshalByRefObject類(按引用封送)支持遠程處理的應用程序中跨應用程序域(AppDomain)邊界訪問對象,同一應用程序域中的對象直接通信。不同應用程序域中的對象的通信方式有兩種:跨應用程序域邊界傳輸對象副本、通過代理交換消息,MarshalByRefObject類本質上通過引用代理交換消息來跨應用程序域邊界進行通信的對象的基類。
3.2、服務端
創建服務端的通道分為HttpServerChannel、HttpChannel,其中HttpServerChannel類有多個重載方法,需要知道和漏洞相關的兩個重載是發生在參數IServerChannelSinkProvider,它表示服務端遠程消息流的通道接收器
IServerChannelSinkProvider派生出多個類,例如BinaryServerFormatterSinkProvider、SoapServerFormatterSinkProvider類,如下圖
SoapServerFormatterSinkProvider類實現了這個介面,並使用SoapFormatter格式化器序列化對象,如下圖
SoapFormatter格式化器實現了System.Runtime.Serialization.IFormatter介面,IFormatter介面包括了Serialize、Deserialize方法,提供了序列化對象圖的功能。
在序列化的時候調用格式化器的Serialize方法,傳遞對流對象的引用和想要序列化的對象圖引用的兩個參數,流對象可以是從System.IO.Stream類派生出來的任意對象,比如常見的MemoryStream、FileStream等,簡單的說就是通過格式化器的Serialize方法可將對象圖中所有對象都被序列化到流裡去,通過Deserialize方法將流反序列化為對象圖。
介紹完SoapFormatter之後回過頭來繼續看SoapServerFormatterSinkProvider類,它有一個重要的内容TypeFilterLevel,表示當前自動反序列化級別,支持的值為Low(默認)和FULL。
當取值為Low的時候,代表.NET Framework遠程處理較低的反序列化級別,只支持基本遠程處理功能相關聯的類型,而取值為Full的時候則支持所有類型在任意場景下遠程處理都支持,所以取值為Full的時候,存在著嚴重的安全風險。
梳理一下HTTP通道攻擊的前置條件,第一步實例化SoapServerFormatterSinkProvider類並且設定TypeFilterLevel内容為Full;第二步實例化HttpServerChannel/HttpChannel類,
使用下列三種重載方法實現傳入參數SoapServerFormatterSinkProvider
- 滿足攻擊者需求的第1個攻擊重載方法是public HttpServerChannel(IDictionary properties,IServerChannelSinkProvider sinkProvider);
這裡筆者用VulnerableDotNetHTTPRemoting項目中的VulnerableDotNetHTTPRemotingServer類來改寫官方Demo 。IDictionary集合存放當前通道的配寘資訊,如圖
- 滿足攻擊者需求的第2個攻擊重載方法是public HttpServerChannel(string name,int port,IServerChannelSinkProvider sinkProvider);
- 滿足攻擊者需求的第3個攻擊方法是位於HttpChannel類下的public HttpChannel(IDictionary properties,IClientChannelSinkProvider clientSinkProvider,IServerChannelSinkProvider serverSinkProvider)
VulnerableDotNetHTTPRemoting項目中用到就是第三種攻擊方法,由於.NET Remoting用戶端在攻擊中用途不大,故筆者不做贅述。
0x04打造Poc
國外研究者發現Microsoft.VisualStudio.Text.UI.Wpf.dll中的Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties類實現了ISerializable介面,這個介面可以對序列化/反序列化的數據進行完全的控制,並且還避免了反射機制,但有個問題Microsoft.VisualStudio.Text.UI.Wpf.dll需要安裝VisualStudio,在非開發主機上不會安裝,但研究者後來發現Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties類在Windows默認安裝的Microsoft.PowerShell.Editor.dll裏也同樣存在,反編譯得到源碼,
實現了ISerializable介面,ISerializable只有一個方法,即GetObjectData,如果一個對象的類型實現了ISerializable介面,會構造出新的System.Runtime.Serialization.SerializationInfo對象,這個對象包含了要為對象序列化的值的集合。
GetObjectData方法的功能是調用SerializationInfo類型提供的SetType方法設定類型轉換器,使用提供的AddValue多個重載方法來指定要序列化的資訊,針對要添加的添加的每個數據,都要調用一次AddValue,GetObjectData添加好所有必要的序列化資訊後會返回到類型解析器,類型解析器獲取已經添加到SerializationInfo對象的所有值,並把他們都序列化到流中,程式碼邏輯實現部分參攷如下
TextFormattingRunProperties類中的ForegroundBrush内容支持XAML數據,攻擊者可以引入《.NET高級代碼審計(第一課)XmlSerializer反序列化漏洞》同樣的攻擊載荷,如下
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Diag="clr-namespace:System.Diagnostics;assembly=system">
<ObjectDataProvider x:Key="LaunchCalc" ObjectType = "{ x:Type Diag:Process}" MethodName = "Start" >
<ObjectDataProvider.MethodParameters>
<System:String>cmd</System:String>
<System:String>/c "calc" </System:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>
又因為SoapServerFormatterSinkProvider類用SoapFormatter格式化器處理數據,所以用戶端提交的數據肯定是SOAP消息,SOAP是基於XML的簡易協定,讓應用程序在HTTP上進行資訊交換用的。為了給出標準的SOAP有效負載,筆者參攷微軟官方給的Demo
結合Soroush Dalili(@irsdl)給出的有效載荷,元素a1指向的命名空間正是TextFormattingRunProperties類所在空間地址
xmlns:a1="http://schemas.microsoft.com/clr/nsassem/Microsoft.VisualStudio.Text.Formatting/Microsoft.PowerShell.Editor%2C%20Version%3D3.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3D31bf3856ad364e35"
在<a1:TextFormattingRunProperties></a1:TextFormattingRunProperties>元素內添加了内容ForegroundBrush,在ForegroundBrush元素內帶入ResourceDictionary,這樣SOAP消息的攻擊載荷主體就完成了。@irsdl給出的有效載荷如下
由於.NET Remoting只支持SOAP 1.1,所以要指定SOAPAction,說來也奇怪這個SOAPAction的值是個URI,但是這個URI不必對應實際的位置。SOAPAction Header選項在SOAP1.2版本已經移除。另外一點圖上請求URI中的副檔名是rem,如果生產環境部署在IIS裏,默認調用.NET應用模塊IsapiModule來處理HttpRemoting,所以在白盒稽核或者黑盒滲透的時候遇到rem副檔名,就得考慮可能開啟了.NET Remoting應用。
還有一處需要注意,HTTP請求有個擴展方法M-POST,其中的其中的M表示Mandatory(必須遵循的,強制的),如果一個HTTP請求包含至少一個強制的擴充聲明,那麼這個請求就稱為強制的請求。強制請求的請求方法名字必須帶有“M-”首碼,例如,強制的POST方法稱為M-POST,這樣的請求管道或許能更好的躲避和穿透防護設備。
0x05程式碼稽核
5.1、SoapServerFormatterSinkProvider
從SoapServerFormatterSinkProvider類分析來看,需要滿足内容TypeFilterLevel的值等於TypeFilterLevel.Full,可觸發的通道包括了HttpChannel類、HttpServerChannel類,這個攻擊點的好處在於發送HTTP SOAP消息,可很好的穿透防火牆。
5.2、BinaryServerFormatterSinkProvider
從BinaryServerFormatterSinkProvider類分析來看,也需要滿足内容TypeFilterLevel的值等於TypeFilterLevel.Full,可觸發的通道包括了TcpChannel類、TcpServerChannel類,這個攻擊點可反序列化二進位檔案,筆者由於時間倉促,暫時不做分析跟進,有興趣的朋友可自行研究。
0x06複盤
筆者將VulnerableDotNetHTTPRemoting項目部署到虛擬機器,運行Server端,打開了本地埠1234
Burpsuite請求後成功彈出小算盘,感謝Soroush Dalili(@irsdl)的分享。
0x07總結
.NET Remoting科技已經出來很多年了,現在微軟主推WCF來替代它,在開發中使用概念越來越低,從漏洞本身看只要沒有設定SoapServerFormatterSinkProvider類内容TypeFilterLevel=Full就不會產生反序列化攻擊(默認就是安全的)最後.NET反序列化系列課程筆者會同步到https://github.com/Ivan1ee/、https://ivan1ee.gitbook.io/,後續筆者將陸續推出高品質的.NET反序列化漏洞文章,歡迎大夥持續關注,交流,更多的.NET安全和技巧可關注實驗室公眾號。
0x08參攷
https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2019/march/finding-and-exploiting-.net-remoting-over-http-using-deserialisation/
https://docs.microsoft.com/zh-cn/previous-versions/4abbf6k0(v=vs.120)
https://github.com/nccgroup/VulnerableDotNetHTTPRemoting