DotNet安全-Exchange请求流程分析(二)

渗透技巧 2年前 (2022) admin
587 0 0

DotNet安全-Exchange请求流程分析(二)

引言

在研究exchange反序列化漏洞CVE-2021–42321、CVE-2020-17144和CVE-2018-8302之前,我们知道这几个漏洞都是通过ews接口设置账户属性为二进制数据后触发的反序列化。ews是exchange提供给用户的webservice接口,包含了绝大多数的用户功能,研究ews的业务逻辑还是很有必要的。结合前一篇文章,本篇主要介绍ews的后端处理部分。

WCF架构

wcf笔者个人的理解是一种基于http协议的RPC手段,类似于java中的jndi、rmi等等。主要为了实现分布式架构而产生的技术。

定义接口

using System;
using System.ServiceModel;
 
namespace GettingStartedLib
{
        [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
        public interface ICalculator
        {
            [OperationContract]
            double Add(double n1, double n2);
                  }
}

关键字OperationContract,看到这个就知道是服务的的接口。

实现接口

using System;
using System.ServiceModel;
 
namespace GettingStartedLib
{
    public class CalculatorService : ICalculator
    {
        public double Add(double n1, double n2)
        {
            double result = n1 + n2;
            Console.WriteLine("Received Add({0},{1})", n1, n2);
            // Code added to write output to the console window.
            Console.WriteLine("Return: {0}", result);
            return result;
        }
 
       }
}

修改配置文件

DotNet安全-Exchange请求流程分析(二)
img

创建注册端点

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using GettingStartedLib;
 
namespace GettingStartedHost
{
    class Program
    {
        static void Main(string[] args)
        {
            // Step 1: Create a URI to serve as the base address.
            Uri baseAddress = new Uri("http://localhost:8000/GettingStarted/");
 
            // Step 2: Create a ServiceHost instance.
            ServiceHost selfHost = new ServiceHost(typeof(CalculatorService), baseAddress);
 
            try
            {
                // Step 3: Add a service endpoint.
                selfHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "CalculatorService");
 
                // Step 4: Enable metadata exchange.
                ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
                smb.HttpGetEnabled = true;
                selfHost.Description.Behaviors.Add(smb);
 
                // Step 5: Start the service.
                selfHost.Open();
                Console.WriteLine("The service is ready.");
 
                // Close the ServiceHost to stop the service.
                Console.WriteLine("Press <Enter> to terminate the service.");
                Console.WriteLine();
                Console.ReadLine();
                selfHost.Close();
            }
            catch (CommunicationException ce)
            {
                Console.WriteLine("An exception occurred: {0}", ce.Message);
                selfHost.Abort();
            }
        }
    }
}

这是conoleApp的流程,实际web应用在web.config定义即可:

<?xml version="1.0" encoding="utf-8"?>  
<configuration>  
 <system.serviceModel>  
  <bindings>  
    <basicHttpBinding>  
     <binding name="myBindingConfiguration1" closeTimeout="00:01:00" />  
     <binding name="myBindingConfiguration2" closeTimeout="00:02:00" />  
     <binding closeTimeout="00:03:00" />  <!-- Default binding for basicHttpBinding -->  
    </basicHttpBinding>  
     </bindings>  
     <services>  
      <service name="MyNamespace.myServiceType">  
       <endpoint
          address="myAddress" binding="basicHttpBinding"
          bindingConfiguration="myBindingConfiguration1"  
          contract="MyContract"  />  
       <endpoint
          address="myAddress2" binding="basicHttpBinding"
          bindingConfiguration="myBindingConfiguration2"  
          contract="MyContract" />  
       <endpoint
          address="myAddress3" binding="basicHttpBinding"
          contract="MyContract" />  
       </service>  
      </services>  
    </system.serviceModel>  
</configuration>  

客户端调用

客户端配置:

 <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <startup>
            <!-- specifies the version of WCF to use-->
            <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
        </startup>
        <system.serviceModel>
            <bindings>
                <!-- Uses wsHttpBinding-->
                <wsHttpBinding>
                    <binding name="WSHttpBinding_ICalculator" />
                </wsHttpBinding>
            </bindings>
            <client>
                <!-- specifies the endpoint to use when calling the service -->
                <endpoint address="http://localhost:8000/GettingStarted/CalculatorService"
                    binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ICalculator"
                    contract="ServiceReference1.ICalculator" name="WSHttpBinding_ICalculator">
                    <identity>
                        <dns value="localhost" />
                    </identity>
                </endpoint>
            </client>
        </system.serviceModel>
    </configuration>

调用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GettingStartedClient.ServiceReference1;
 
namespace GettingStartedClient
{
    class Program
    {
        static void Main(string[] args)
        {
            //Step 1: Create an instance of the WCF proxy.
            CalculatorClient client = new CalculatorClient();
 
            // Step 2: Call the service operations.
            // Call the Add service operation.
            double value1 = 100.00D;
            double value2 = 15.99D;
            double result = client.Add(value1, value2);
            Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
        }
    }
}

EWS Module

在前面介绍过,在应用执行前先加载module:

DotNet安全-Exchange请求流程分析(二)
web.config

BackendRehydrationModule

Microsoft.Exchange.Security.Authentication.BackendRehydrationModule主要是exch后端鉴权的部分。

直接进入了TryGetCommonAccessToken,从header中获取CommonAccessToken

DotNet安全-Exchange请求流程分析(二)

将token反序列化,存入httpContext.Items中

DotNet安全-Exchange请求流程分析(二)

通过BackendAuthenticator.Rehydrate解析token

DotNet安全-Exchange请求流程分析(二)

跟进,使用InternalRehydrate实现

DotNet安全-Exchange请求流程分析(二)

该函数为抽象方法,实际由Microsoft.Exchange.Security.Authentication.WindowsAuthenticator.InternalRehydrate实现

DotNet安全-Exchange请求流程分析(二)

从CommonAccessToken获取到了principal

DotNet安全-Exchange请求流程分析(二)

随后进入TryHandleRehydratedIdentity函数,检查身份是否合规:

DotNet安全-Exchange请求流程分析(二)

最终将令牌给httpContext.User

DotNet安全-Exchange请求流程分析(二)

该模块主要功能是从FrontEnd提供的CommonAcessToken中提取出用户令牌。

WCF部分:

Exchange.asmx

<%@ServiceHost Service="Microsoft.Exchange.Services.Wcf.EWSService" Factory="Microsoft.Exchange.Services.Wcf.EWSServiceHostFactory" %>
DotNet安全-Exchange请求流程分析(二)
img

EWSService实现了IEWSContract和IEWSStreamingContract,是wcf服务的具体实现。

DotNet安全-Exchange请求流程分析(二)
img

网上简单搜了下,不知道怎么将asmx和wcf整合使用。从后面的了解来看感觉BackEnd既是wcf服务端也是wcf客户端。这块不明白对后面整个流程的梳理也没有什么问题。

ISAPI

对于.asmx,由isapi判断由Microsoft.Exchange.Services.DispatchPipe.Ews.EwsServiceHttpHandlerFactory负责处理:

DotNet安全-Exchange请求流程分析(二)
img

EwsServiceHttpHandlerFactory继承自HttpHandlerFactoryBase,实现EWSSERVICE接口

DotNet安全-Exchange请求流程分析(二)
img

HttpHandlerFactoryBase继承自IHttpHandlerFactory:

https://docs.microsoft.com/en-us/dotnet/api/system.web.ihttphandlerfactory.gethandler?view=netframework-4.8

根据微软例子,继承了IHttpHandlerFactory的HandlerFactory类,重写GetHandler方法。根据iis的配置:

<configuration>   
  <system.web>   
    <httpHandlers>   
      <add verb="*" path="abc.aspx" type="test.MyFactory,HandlerFactoryTest" />   
      <add verb="*" path="xyz.aspx" type="test.MyFactory,HandlerFactoryTest" />   
    </httpHandlers>   
  </system.web>  
</configuration>  

比如path的文件名,提供不同的hanlder:

 public virtual IHttpHandler GetHandler(HttpContext context,
                                             String requestType,
                                             String url,
                                             String pathTranslated)
      {
         String fname = url.Substring(url.LastIndexOf('/')+1);
         String cname = fname.Substring(0, fname.IndexOf('.'));
         String className = "test." + cname;
 
         Object h = null;
 
         // Try to create the handler object.
         try
         {
            // Create the handler by calling class abc or class xyz.
            h = Activator.CreateInstance(Type.GetType(className));
         }
         catch(Exception e)
         {
            throw new HttpException("Factory couldn't create instance " +
                                    "of type " + className, e);
         }
         return (IHttpHandler)h;
      }

所以我们先看EwsServiceHttpHandlerFactory的GetHandler方法:

DotNet安全-Exchange请求流程分析(二)

通过SelectOperation函数确定method,之后再生成对应的hanlder,我们看SelectOperation的逻辑:

DotNet安全-Exchange请求流程分析(二)

总之就是解析xml里面的方法,之后创建service,实际上就是EWSservice的实例,再创建对应的hanlder

DotNet安全-Exchange请求流程分析(二)

创建HTTPhanlder,所有的ews方法都是异步的,所以asynchanlder:

DotNet安全-Exchange请求流程分析(二)

获得了Microsoft.Exchange.Services.DispatchPipe.Ews. EwsServiceHttpAsyncHandler的一个实例。

这里没法打断点,不知道为什么,如果asynchanlder实例获取失败了,就获取wcfHttpHanlder:

DotNet安全-Exchange请求流程分析(二)

可以看到最终的hanlder为wcf的提供的功能:

DotNet安全-Exchange请求流程分析(二)

System.ServiceModel.Activation.ServiceHttpHandlerFactory具体怎么实现的没有什么公开的文档,我们可以推测实际最后都是调用EWSservice里的方法,比如GetFolder:

DotNet安全-Exchange请求流程分析(二)

对应方法会调用submit,一共三个参数,第一个调用上下文,应该包含身份信息等。第二个应该是成功后的回调,第三个不知道。我们比较关注callContext。

DotNet安全-Exchange请求流程分析(二)

callContext由工厂类创建:

DotNet安全-Exchange请求流程分析(二)

callContext生成过程非常复杂,以后再详细研究。

DotNet安全-Exchange请求流程分析(二)

将三个参数传入submit准备调用,发现检查了一次Impersonate权限。测试从Negotiate认证获得的身份是system:

DotNet安全-Exchange请求流程分析(二)

之后创建异步任务并调用:

DotNet安全-Exchange请求流程分析(二)

进入 Microsoft.Exchange.Services.Core.Types.BaseServiceTask触发执行

DotNet安全-Exchange请求流程分析(二)

走到Microsoft.Exchange.Services.Core.Types.ServiceTask的InternalExecute方法:

DotNet安全-Exchange请求流程分析(二)

走到Microsoft.Exchange.Services.Core.BaseStepServiceCommand的InternalExecuteStep方法:

DotNet安全-Exchange请求流程分析(二)

通过这个方法调用Microsoft.Exchange.Services.Core中各个类的Execute方法。

小结

这样就明了了,soap数据包中带的method与Microsoft.Exchange.Services.Core中的各个类是对应的。如要调用Microsoft.Exchange.Services.Core.GetFolder方法就应该在soap的method写GetFolder。请求的参数和Microsoft.Exchange.Services.Core.Types中的对象对应,如Microsoft.Exchange.Services.Core.Types.ServiceUserConfiguration:

DotNet安全-Exchange请求流程分析(二)
img

对应的xml为:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
<t:RequestServerVersion Version="Exchange2013" />
  </soap:Header>
  <soap:Body>
<m:CreateUserConfiguration>
  <m:UserConfiguration>
<t:UserConfigurationName Name="ExtensionMasterTable">
  <t:FolderId Id="%s" ChangeKey="%s" />
</t:UserConfigurationName>
<t:Dictionary>
  <t:DictionaryEntry>
  </t:DictionaryEntry>
  </t:Dictionary>
<t:BinaryData>%s</t:BinaryData>
  </m:UserConfiguration>
</m:CreateUserConfiguration>
  </soap:Body>
</soap:Envelope>

至此我们明白了从ews的soap如何对应到具体的函数逻辑,以及如何生成请求的xml。同时也知道的Common-AccessToken的验证流程。

总结

知道了对应soap的功能在哪里后,我们就可以针对功能去审计代码。同时可以反射调用这些功能,绕过目标的AV/EDR。


原文始发于微信公众号(7bits安全团队):DotNet安全-Exchange请求流程分析(二)

版权声明:admin 发表于 2022年10月8日 上午9:11。
转载请注明:DotNet安全-Exchange请求流程分析(二) | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...