0x00 前言
最近几天国外安全研究员Soroush Dalili (@irsdl)公布了.NET Remoting应用程序可能存在反序列化安全风险,当服务端使用HTTP信道中的SoapServerFormatterSinkProvider类作为信道接收器并且将自动反序列化TypeFilterLevel属性设置为Full的时候会造成反序列化漏洞,从而实现远程RCE攻击,本文笔者从原理和代码审计的视角做了相关介绍和复现,并且归纳成.NET反序列化漏洞系列课程中的第五课
0x01 .NET Remoting概念
.NET Remoting是一种分布式应用解决方案,它允许不同AppDomain(应用程序域)之间进行通信,这里的通信可以是在同一个进程中进行或者一个系统中的不同进程间进行的通信。.NET Remoting框架也提供了多种服务,包括激活和生存期支持,以及负责与远程应用程序进行消息传输的通道。应用程序可在重视性能的场景下使用二进制数据传输,在需要与其他远程处理框架进行交互的场景下使用 XML 数据传输。在从一个AppDomain向另一个AppDomain传输消息时,所有的XML数据都使用 SOAP 协议,总体看.NET Remoting有以下三点优势:
0x02 .NET Remoting信道和协议
信道是Server和Client进行通信用的,在.NET Remoting中提供了三种信道类型,
IpcChannel提供了使用Windows进程间通信(IPC)系统在同一计算机上的应用程序域之间传输消息的机制。在同一计算机上的应用程序域之间进行通信时,IPC信道比TCP或HTTP信道要快得多。但是IPC只在本机应用之间通信。所以,在客户端和服务端在同一台机器时,我们可以通过注册IpcChannel来提高Remoting的性能。但如果客户端和服务端不在同一台机器时,我们不能注册IPCChannel,在此不多介绍。
TcpChannel提供了基于Socket 的传输工具,使用Tcp协议来跨越Remoting边界传输序列化的消息流。默认使用二进制格式序列化消息对象,具有更高的传输性能,适用于局域网。
HttpChannel提供了一种使用Http协议,使其能在Internet上穿透防火墙传输序列化消息流,HttpChannel类型使用Soap格式序列化消息对象,因此它具有更好的互操作性。适用于广域网,如图
0x03 攻击原理
研究漏洞之前先普及下HttpChannel的相关基础知识,HttpChannel类使用 SOAP 协议在远程对象之间传输消息,并且符合SOAP1.1的标准,所有的消息都是通过SoapFormatter传递,此格式化器会将消息转换为 XML数据并进行序列化,同时向数据流中添加所需的SOAP标头。如果指定了二进制格式化程序,则会创建二进制数据流。随后,将使用 HTTP 协议将数据流传输至目标URI。HttpChannel分类如图
下面是从微软文档里摘取定义服务端的代码片段:
每行代码分别实现了创建服务端通道并且绑定本地端口9090;注册服务端通道;以及通过访问URI为RemoteObject.rem的地址调用远程的对象,在.NET Remoting中有个激活方式的概念,表示在访问远程类型的一个对象实例之前,必须通过一个名为Activation的进程创建它并进行初始化。代码中引入了服务端激活的WellKnown方式,看下图
WellKnown理解为知名对象的激活,服务器应用程序在激活对象实例之前会在统一资源标识符(URI)上来发布这个类型。然后该服务器进程会为此类型配置一个WellKnown对象,并根据指定的端口或地址来发布对象,它的激活分为SingleTon模式 、SingleCall模式,SingleTon类所代表的类型规定每个AppDomain只能存在一个实例,当SingleTon类型加载到AppDomain的时候,CLR调用它的静态构造器去构造一个SingleTon对象,并将它的引用保存到静态字段中,而且该类也没有提供任何的公共构造器方法,这就防止了其他任何代码构造该类的其他实例。具体到这两种模式各有区别,都可以触发漏洞,因不是重点所以不做过多介绍。
3.1、远程对象
图中的RemoteObject类,这是一个远程对象,看下微软官方的定义
RemoteObject继承自MarshalByRefObject类,MarshalByRefObject类(按引用封送)支持远程处理的应用程序中跨应用程序域(AppDomain)边界访问对象,同一应用程序域中的对象直接通信。不同应用程序域中的对象的通信方式有两种:跨应用程序域边界传输对象副本、通过代理交换消息,MarshalByRefObject类本质上通过引用代理交换消息来跨应用程序域边界进行通信的对象的基类。
3.2、服务端
创建服务端的信道分为HttpServerChannel、HttpChannel,其中HttpServerChannel类有多个重载方法,需要知道和漏洞相关的两个重载是发生在参数IServerChannelSinkProvider,它表示服务端远程消息流的信道接收器
IServerChannelSinkProvider派生出多个类,例如BinaryServerFormatterSinkProvider、SoapServerFormatterSinkProvider类,如下图
SoapServerFormatterSinkProvider类实现了这个接口,并使用SoapFormatter格式化器序列化对象,如下图
SoapFormatter格式化器实现了System.Runtime.Serialization.IFormatter接口,IFormatter接口包括了Serialize、Deserialize方法,提供了序列化对象图的功能。
在序列化的时候调用格式化器的Serialize方法,传递对流对象的引用和想要序列化的对象图引用的两个参数,流对象可以是从System.IO.Stream类派生出来的任意对象,比如常见的MemoryStream、FileStream等,简单的说就是通过格式化器的Serialize方法可将对象图中所有对象都被序列化到流里去,通过Deserialize方法将流反序列化为对象图。
介绍完SoapFormatter之后回过头来继续看SoapServerFormatterSinkProvider类,它有一个重要的属性TypeFilterLevel,表示当前自动反序列化级别,支持的值为Low(默认)和FULL。
当取值为Low的时候,代表.NET Framework 远程处理较低的反序列化级别,只支持基本远程处理功能相关联的类型,而取值为Full的时候则支持所有类型在任意场景下远程处理都支持,所以取值为Full的时候,存在着严重的安全风险。
梳理一下HTTP信道攻击的前置条件,第一步实例化SoapServerFormatterSinkProvider类并且设置TypeFilterLevel属性为Full;第二步实例化HttpServerChannel/HttpChannel类,
使用下列三种重载方法实现传入参数SoapServerFormatterSinkProvider
- 满足攻击者需求的第1个攻击重载方法是public HttpServerChannel(IDictionary properties, IServerChannelSinkProvider sinkProvider);
这里笔者用VulnerableDotNetHTTPRemoting项目中的VulnerableDotNetHTTPRemotingServer类来改写官方Demo 。IDictionary集合存放当前通道的配置信息,如图
- 满足攻击者需求的第2个攻击重载方法是public HttpServerChannel(string name, int port, IServerChannelSinkProvider sinkProvider);
- 满足攻击者需求的第3个攻击方法是位于HttpChannel类下的 public HttpChannel(IDictionary properties, IClientChannelSinkProvider clientSinkProvider, IServerChannelSinkProvider serverSinkProvider)
VulnerableDotNetHTTPRemoting项目中用到就是第三种攻击方法,由于.NET Remoting客户端在攻击中用途不大,故笔者不做赘述。
0x04 打造Poc
国外研究者发现Microsoft.VisualStudio.Text.UI.Wpf.dll 中的Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties类实现了ISerializable接口,这个接口可以对序列化/反序列化的数据进行完全的控制,并且还避免了反射机制, 但有个问题Microsoft.VisualStudio.Text.UI.Wpf.dll需要安装VisualStudio ,在非开发主机上不会安装,但研究者后来发现Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties类在Windows默认安装的Microsoft.PowerShell.Editor.dll里也同样存在,反编译得到源码,
实现了ISerializable接口,ISerializable只有一个方法,即 GetObjectData,如果一个对象的类型实现了ISerializable接口,会构造出新的System.Runtime.Serialization.SerializationInfo对象,这个对象包含了要为对象序列化的值的集合。
GetObjectData方法的功能是调用SerializationInfo类型提供的SetType方法设置类型转换器,使用提供的AddValue多个重载方法来指定要序列化的信息,针对要添加的每个数据,都要调用一次AddValue,GetObjectData添加好所有必要的序列化信息后会返回到类型解析器,类型解析器获取已经添加到SerializationInfo对象的所有值,并把他们都序列化到流中,代码逻辑实现部分参考如下
TextFormattingRunProperties类中的ForegroundBrush属性支持XAML数据,攻击者可以引入《.NET高级代码审计(第一课) XmlSerializer反序列化漏洞》同样的攻击载荷,如下
又因为SoapServerFormatterSinkProvider类用SoapFormatter格式化器处理数据,所以客户端提交的数据肯定是SOAP消息,SOAP是基于XML的简易协议,让应用程序在HTTP上进行信息交换用的。为了给出标准的SOAP有效负载,笔者参考微软官方给的Demo
结合Soroush Dalili (@irsdl)给出的有效载荷,元素a1指向的命名空间正是TextFormattingRunProperties类所在空间地址
在<a1:TextFormattingRunProperties></a1:TextFormattingRunProperties>元素内添加了属性ForegroundBrush,在ForegroundBrush元素内带入ResourceDictionary,这样SOAP消息的攻击载荷主体就完成了。@irsdl给出的有效载荷如下
由于.NET Remoting只支持SOAP 1.1,所以要指定SOAPAction,说来也奇怪这个SOAPAction的值是个URI,但是这个URI不必对应实际的位置。SOAPAction Header选项在SOAP1.2版本已经移除。另外一点图上请求URI中的扩展名是rem,如果生产环境部署在IIS里,默认调用.NET应用模块IsapiModule来处理HttpRemoting,所以在白盒审计或者黑盒渗透的时候遇到rem扩展名,就得考虑可能开启了.NET Remoting应用。
还有一处需要注意,HTTP请求有个扩展方法M-POST,其中的其中的M表示Mandatory(必须遵循的,强制的),如果一个HTTP请求包含至少一个强制的扩充声明,那么这个请求就称为强制的请求。强制请求的请求方法名字必须带有“M-”前缀,例如,强制的POST方法称为M-POST,这样的请求方式或许能更好的躲避和穿透防护设备。
0x05 代码审计
5.1、SoapServerFormatterSinkProvider
从SoapServerFormatterSinkProvider类分析来看,需要满足属性TypeFilterLevel的值等于TypeFilterLevel.Full,可触发的通道包括了HttpChannel类、HttpServerChannel类,这个攻击点的好处在于发送HTTP SOAP消息,可很好的穿透防火墙。
5.2、BinaryServerFormatterSinkProvider
从BinaryServerFormatterSinkProvider类分析来看,也需要满足属性TypeFilterLevel的值等于TypeFilterLevel.Full,可触发的通道包括了TcpChannel类、TcpServerChannel类,这个攻击点可反序列化二进制文件,笔者由于时间仓促,暂时不做分析跟进,有兴趣的朋友可自行研究。
0x06 复盘
笔者将VulnerableDotNetHTTPRemoting项目部署到虚拟机,运行Server端,打开了本地端口1234
Burpsuite请求后成功弹出计算器,感谢Soroush Dalili (@irsdl)的分享。
0x07 总结
.NET Remoting技术已经出来很多年了,现在微软主推WCF来替代它,在开发中使用概率越来越低,从漏洞本身看只要没有设置SoapServerFormatterSinkProvider类属性TypeFilterLevel=Full就不会产生反序列化攻击(默认就是安全的)最后.NET反序列化系列课程笔者会同步到 https://github.com/Ivan1ee/ 、https://ivan1ee.gitbook.io/ ,后续笔者将陆续推出高质量的.NET反序列化漏洞文章,欢迎大伙持续关注,交流,更多的.NET安全和技巧可关注实验室公众号。