在研究exchange反序列化漏洞CVE-2021–42321、CVE-2020-17144和CVE-2018-8302之前,我们知道这几个漏洞都是通过ews接口设置账户属性为二进制数据后触发的反序列化。ews是exchange提供给用户的webservice接口,包含了绝大多数的用户功能,研究ews的业务逻辑还是很有必要的。结合前一篇文章,本篇主要介绍ews的后端处理部分。
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);
}
}
}
在前面介绍过,在应用执行前先加载module:
web.config
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中提取出用户令牌。
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客户端。这块不明白对后面整个流程的梳理也没有什么问题。
对于.asmx,由isapi判断由Microsoft.Exchange.Services.DispatchPipe.Ews.EwsServiceHttpHandlerFactory负责处理:
img
EwsServiceHttpHandlerFactory继承自HttpHandlerFactoryBase,实现EWSSERVICE接口
img
HttpHandlerFactoryBase继承自IHttpHandlerFactory:
根据微软例子,继承了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。