长亭百川云 - 文章详情

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

7bits安全团队

71

2024-07-13

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;  
        }  
   
       }  
}

修改配置文件

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:

web.config

BackendRehydrationModule

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

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

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

通过BackendAuthenticator.Rehydrate解析token

跟进,使用InternalRehydrate实现

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

从CommonAccessToken获取到了principal

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

最终将令牌给httpContext.User

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

WCF部分:

Exchange.asmx

<%@ServiceHost Service="Microsoft.Exchange.Services.Wcf.EWSService" Factory="Microsoft.Exchange.Services.Wcf.EWSServiceHostFactory" %>

img

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

img

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

ISAPI

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

img

EwsServiceHttpHandlerFactory继承自HttpHandlerFactoryBase,实现EWSSERVICE接口

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方法:

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

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

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

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

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

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

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

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

callContext由工厂类创建:

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

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

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

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

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

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

通过这个方法调用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:

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。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2