exchange proxylogon漏洞学习

本文深入剖析了Exchange客户端访问服务(CAS)的工作原理,重点分析了Frontend的ProxyModule和Backend的RehydrationModule,阐述了它们在请求处理中的角色。同时,详细讲解了CVE-2021-26855 SSRF漏洞和CVE-2021-27065认证后任意文件写入漏洞的利用机制。通过对HttpProxyProxyRequestHandler.cs和BackendRehydrationModule.cs的代码分析,揭示了攻击点和防御措施。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 前言

Exchange服务已经存在很多年,架构一直在变化而且愈来愈复杂,与此同时出现问题的可能性也更多了。

CAS模块,在Exchange 2016上正式成为Mailbox Role上的一个服务,ProxyLogon由此模块开始。

img

2. 理解CAS

CAS,全名是客户端访问服务。一句话概括,CAS是一个Exchange服务端代理,运行在443端口,真正的服务运行在exchange服务器的444端口。它可以代理所有协议的客户端访问,并将流量输送到服务端也就是Backend services。在代理的时候除了客户端原本的请求信息外,CAS还会自己在请求包中加一些数据。

cas中有许多服务例如autodiscover,他们一起构成了frontend,但他们都不是服务本身。不同的服务都有自己专属的proxy module,这些proxy module的作用是将流量传送给后端(backend)真正的服务。

真正的访问流程应该如下:

client<>echange:443(CAS也可以叫做frontend)<>exchange:444(backend)

img

CAS web端是基于IIS的,里面含有很多服务,例如POP3等如下图:

img

我们可知道Default Web Site是绑定在80和443端口,并且绑定在0.0.0.0的也就意味着所有人都可访问,Default Web Site可以理解成CAS所有服务的集合体,也可以叫做Frontend

而Back End在81、444端口,绑定的ip也是0.0.0.0,也是所有人都可以访问的。


Frontend必须有proxy module,它用来接收客户端发送的http请求然后加上一些配置后再发送给Backend。

Backend一定会有Rehydration Module模块,它是用来负责解析Frontend中proxy module发送过来的请求的,并将请求传送给真正的服务进行处理然后将结果返回给Frontend中的proxy module。

综上,CAS里有许多proxy module,这些模块负责接收流量,并将其正确的传送给backend。而backend的Rehydration Module模块负责接收这些流量并进行处理。整体通行过程是客户与CAS中的proxy module交互,proxy module与rehydration module交互。

2.1 理解Frontend中的proxy module

不同的服务有不同的proxy module,例如我们访问ews服务,对应的proxy module就是EwsProxyRequestHandler,EwsProxyRequestHandler和其他对应服务的handler都是继承了ProxyRequestHandler,然后实现自己的核心逻辑,例如,如何处理来自用户的HTTP请求,什么url要代理到Backend,以及如何与backend同步信息。

总的来说最重要的也就是这个父类ProxyRequestHandler了,因此我们好好分析一下他。根据ProxyRequestHandler.cs的代码,我们可以将其分为三部分:Request section、proxy section、Response section。

2.1.1 分析ProxyRequestHandler-Request section

Request Section是用来解析来自client的http请求的,它会接受请求并http请求是否合法,如果合法则会保留请求并添加客户端的身份信息到X-CommonAccessToken头中。

Frontend根Backend都是依靠http header来同步信息和代理内部状态的。因此,Exchange定义了一个黑名单,以避免一些内部Headers被误用。

HttpProxy\ProxyRequestHandler.cs

protected virtual bool ShouldCopyHeaderToServerRequest(string headerName) {
  return !string.Equals(headerName, "X-CommonAccessToken", OrdinalIgnoreCase) 
      && !string.Equals(headerName, "X-IsFromCafe", OrdinalIgnoreCase) 
      && !string.Equals(headerName, "X-SourceCafeServer", OrdinalIgnoreCase) 
      && !string.Equals(headerName, "msExchProxyUri", OrdinalIgnoreCase) 
      && !string.Equals(headerName, "X-MSExchangeActivityCtx", OrdinalIgnoreCase) 
      && !string.Equals(headerName, "return-client-request-id", OrdinalIgnoreCase) 
      && !string.Equals(headerName, "X-Forwarded-For", OrdinalIgnoreCase) 
      && (!headerName.StartsWith("X-Backend-Diag-", OrdinalIgnoreCase) 
      || this.ClientRequest.GetHttpRequestBase().IsProbeRequest());
}

也就意味着出现上述任何一个http header,这个http请求都不会被转发给backend。

在request的最后一步,会调用一个叫做AddProtocolSpecificHeadersToServerRequest的函数来加上一些需要与Backend进行交互的信息,并且会将当前用户的身份信息序列化后放到一个名为X-CommonAccessToken的http头中,例如,如果我使用名称 Orange 登录 Outlook Web Access (OWA),则X-CommonAccessToken前端到后端的代理将是:

img

2.1.2 分析ProxyRequestHandler-Proxy Section

首先会使用GetTargetBackendServerURL函数来确定Request section阶段得到的格式化的http请求应该被转发给backend的哪个url。然后使用CreateServerRequest函数来初始化一个新的http请求,具体就是给请求中加上一些http头并填入相关信息,其中有一个比较重要的是Authorization头,这个头的存在是为了阻止未认证用户直接访问Backend,这个头的内容是Kerberos ticket。通过这个头,当请求被传输给backend的时候,backend就可以判断请求是否有效。


综上,发送给backend的http请求中最重要的两个http头是authorization与x-CommonAccessToken,前者代表这个请求时合法的,后者代表访问者的身份信息。如果我们可以伪造这两个头则可以伪装人意用户的身份访问backend。

2.1.3 分析ProxyRequestHandler-Response Section

这一部分的作用是接收Backend发送过来的信息,并确定什么头或者cookie可以发送给frontend。

2.2 理解Backend中的Rehydration Module

这个模块被BackendRehydrationModule.cs中的代码实现。

当接收到ProxyRequestHandler发送过来的http请求的时候,Rehydration Module首先会使用IsAuthenticated函数判断这个请求是否经过身份认证,然后判断是否有一个叫做ms-Exch-EPI-Token-Serialization的扩张权限,当两者都通过验证的时候,才算验证成功。一般说来只有Exchange的服务账号(机器账号)才能通过验证。

通过验证后,Exhcange会将请求中的身份信息从Exchange机器账号的身份信息变为X-commonAccessToken中存储的身份信息。然后将其放入httpContext对象中,紧接着执行Backend中相关的其他业务逻辑。

Authentication\BackendRehydrationModule.cs

private void OnAuthenticateRequest(object source, EventArgs args) {
    if (httpContext.Request.IsAuthenticated) {
        this.ProcessRequest(httpContext);
    }
}

private void ProcessRequest(HttpContext httpContext) {
    CommonAccessToken token;
    if (this.TryGetCommonAccessToken(httpContext, out token)) {
        // ...
    }
}

private bool TryGetCommonAccessToken(HttpContext httpContext, out CommonAccessToken token) {
    string text = httpContext.Request.Headers["X-CommonAccessToken"];
    if (string.IsNullOrEmpty(text)) {
        return false;
    }
        
    bool flag;
    try {
        flag = this.IsTokenSerializationAllowed(httpContext.User.Identity as WindowsIdentity);
    } finally {
        httpContext.Items["BEValidateCATRightsLatency"] = stopwatch.ElapsedMilliseconds - elapsedMilliseconds;
    }

    token = CommonAccessToken.Deserialize(text);
    httpContext.Items["Item-CommonAccessToken"] = token;
    
    //...
}

private bool IsTokenSerializationAllowed(WindowsIdentity windowsIdentity) {
   flag2 = LocalServer.AllowsTokenSerializationBy(clientSecurityContext);
   return flag2;
}

private static bool AllowsTokenSerializationBy(ClientSecurityContext clientContext) {
    return LocalServer.HasExtendedRightOnServer(clientContext, 
        WellKnownGuid.TokenSerializationRightGuid);  // ms-Exch-EPI-Token-Serialization

}

综上CAS就是一个Http代理。

3. 攻击点

3.1 cve-2021-26855 SSRF漏洞

HttpProxy\ProxyRequestHandler.cs中有一个函数GetTargetBackEndServerUrl,上面说过这个函数是用来生成要传递给backend的url,也就是最终被访问的url。

我们发现其中一个host是客户端可控的,具体是被客户端请求中cookie中的一个名为X-AnonResource-Backend-Cookie的字段可控的。具体分隔方式是将X-AnonResource-Backend-Cookie的值用~进行分割,前面是fqdn,后面是version。

综上,只要我们控制X-AnonResource-Backend-Cookie字段,进而控制传递给backend的host,进而实现访问人意backend的api,也就形成了SSRF漏洞。

HttpProxy\ProxyRequestHandler.cs

protected virtual Uri GetTargetBackEndServerUrl() {
    this.LogElapsedTime("E_TargetBEUrl");
    Uri result;
    try {
      	//这里就会调用下面HttpProxy\OwaResourceProxyRequestHandler.cs的代码生成urlAnchorMailbox类
        UrlAnchorMailbox urlAnchorMailbox = this.AnchoredRoutingTarget.AnchorMailbox as UrlAnchorMailbox; 
      	
      
        if (urlAnchorMailbox != null) {
            result = urlAnchorMailbox.Url;
        } else {
            UriBuilder clientUrlForProxy = this.GetClientUrlForProxy();
            clientUrlForProxy.Scheme = Uri.UriSchemeHttps;
          // Host的值其实就是BackendServer也就是client发送的http请求中cookie中的X-AnonResource-Backend的值
            clientUrlForProxy.Host = this.AnchoredRoutingTarget.BackEndServer.Fqdn;
            clientUrlForProxy.Port = 444;
            if (this.AnchoredRoutingTarget.BackEndServer.Version < Server.E15MinVersion) {
                this.ProxyToDownLevel = true;
                RequestDetailsLoggerBase<RequestDetailsLogger>.SafeAppendGenericInfo(this.Logger, "ProxyToDownLevel", true);
                clientUrlForProxy.Port = 443;
            }
            result = clientUrlForProxy.Uri;
        }
    }
    finally {
        this.LogElapsedTime("L_TargetBEUrl");
    }
    return result;
}

HttpProxy\OwaResourceProxyRequestHandler.cs

protected override AnchorMailbox ResolveAnchorMailbox() {
   //将X-AnonResource-Backend赋值给httpCookie
    HttpCookie httpCookie = base.ClientRequest.Cookies["X-AnonResource-Backend"];
    if (httpCookie != null) {
      //将httpcookie赋值给saveBackendServer
        this.savedBackendServer = httpCookie.Value;
    }
    if (!string.IsNullOrEmpty(this.savedBackendServer)) {
        base.Logger.Set(3, "X-AnonResource-Backend-Cookie");
        if (ExTraceGlobals.VerboseTracer.IsTraceEnabled(1)) {
            ExTraceGlobals.VerboseTracer.TraceDebug<HttpCookie, int>((long)this.GetHashCode(), "[OwaResourceProxyRequestHandler::ResolveAnchorMailbox]: AnonResourceBackend cookie used: {0}; context {1}.", httpCookie, base.TraceContext);
        }
      	//通过修改将savedbackendserver赋值给BackEndServer的值
        return new ServerInfoAnchorMailbox(BackEndServer.FromString(this.savedBackendServer), this);
    }
    return new AnonymousAnchorMailbox(this);
}

3.2 CVE-2021-27065 认证后的任意文件写入

当我们访问的服务是/ECP且http请求的uri中资源的后缀合法(即以.skin等结尾,具体可看源码)的话,
在这里插入图片描述

就会使用BEResourceRequestHandler来进行url处理,在这种处理模式下,http头中的X-BEResource会对后端访问的真实url产生影响,及X-BEResource的值被~分隔,前面会变成host,后面是version。如下图,后端真正访问的其实是:444/ecp/proxyLogon.ecp#,而version是1:

img

接着利用autodiscover服务,获取administrator的LegacyDN、sid。
再使用proxyLogon获取ASP.NET_SessionId与msExchEcpCanary。
拥有以上参数后我们就可以伪造自己为administrator。
访问/ecpDDI/DDIService.svc服务来实现以administrator的身份来进行任意文件写入。

在这里插入图片描述

4. 参考文章

A New Attack Surface on MS Exchange Part 1 - ProxyLogon!

CVE-2021–26855与CVE-2021–27065漏洞分析及复现 _

proxylogon exp

Exchange “ProxyLogon”系列漏洞分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shanfenglan7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值