MS Exchange的新攻击面,第1部分:ProxyLogon!

匿名者  1201天前

作为世界上最常见的电子邮件解决方案之一,Microsoft Exchange已经成为政府和企业日常运作和安全防护的重要组成部分。今年一月,我们向微软报告了Exchange Server的一系列漏洞,并将其命名为ProxyLogon。ProxyLogon可能是Exchange历史上最严重、影响最大的漏洞。如果您关注行业新闻,那一定听说过这则消息。

在从架构层面研究ProxyLogon时,我们发现这绝不仅仅是一个漏洞,而是一个全新的攻击面,因为以前从来没有人提到过。这个攻击面可以引导黑客或安全研究人员发现更多的漏洞。因此,我们决定深入研究这个攻击面,最终发现了至少8个漏洞。这些漏洞涵盖了服务器端、客户端,甚至是密码漏洞。我们将这些漏洞组合成3种攻击,具体如下所示:

  • ProxyLogon:最著名和最有影响的Exchange漏洞链。   
  • ProxyOracle:可以恢复Exchange用户的任何明文格式的密码的攻击。    
  • PrxoyShell:我们在Pwn2Own 2021上展示的攻击链,用于接管Exchange并获得了
  • 200,000美元的赏金。

我想强调的是,我们在这里揭示所有漏洞都属于逻辑漏洞,这意味着它们比任何内存损坏漏洞更容易被复现和利用。我们已经在美国黑帽大会和DEFCON上展示了我们的研究成果,并赢得了2021年Pwnie Awards的最佳服务器端漏洞。感兴趣的读者可以从这里找到我们的演讲材料:

  • ProxyLogon is Just the Tip of the Iceberg:A New Attack Surface on Microsoft ExchangeServer! [Slides] [Video]

通过了解这个新的攻击面的基础知识,您就不会对我们如此轻松地爆出这么多0day漏洞感到惊讶了!

简介

需要说明的是,这里提到的所有漏洞都是通过负责任的漏洞披露流程报告的,并且Microsoft公司目前已经对这些漏洞进行了修复。您可以从下表中找到有关CVE和报告时间表的更多详细信息。

火狐截图_2021-08-08T05-29-01.829Z.png

[1] 与这个新的攻击面直接相关的漏洞。

[2] Pwn2Own 2021大赛中使用的漏洞。

为什么Exchange Server会成为热门话题?在我看来,整个ProxyLogon攻击面实际上位于Exchange请求处理的早期阶段。例如,如果Exchange的入口所处的阶段为0,而核心业务逻辑所处的阶段为100,那么ProxyLogon则大约是10。同样,由于该漏洞位于开始位置,我相信只要是仔细审查过Exchange安全性的人都能发现该攻击面。因此,我在向微软报告后,曾经在推特上表示过对于“撞洞”的担忧。虽然该漏洞的影响非常巨大,但它的确非常简单,而且位于如此早期的阶段。

你们都知道接下来发生了什么,Volexity在2021年1月初发现一个APT小组正在利用相同的SSRF(CVE-2021-26855)访问用户的电子邮件,并向微软提交了漏洞报告。微软也在3月份发布了紧急补丁。从后来公布的公开信息中,我们发现,即使他们使用的是相同的SSRF,但APT组织利用该漏洞的方式与我们的方式存在明显差异。我们通过CVE-2021-27065完成了ProxyLogon攻击链,而APT组织则在他们的攻击中利用了EWS和两个未知漏洞。这使我们确信在SSRF漏洞方面的确存在“撞洞”现象。

1.png

对于我们报告给MSRC的ProxyLogon PoC在2月下旬出现在野外一事,我们和大家一样感到奇怪,因为我们通过彻底的调查排除了我们这边泄漏的可能性。随着更清晰的时间表和更多的讨论的出现,我们发现这样的事情似乎不是第一次发生在微软身上了。如果您有兴趣的话,也许可以从这里了解到一些有趣的事情。

为何将矛头指向Exchange Server?

邮件服务器是一种非常有价值的资产,它保存着最机密的秘密和公司数据。换句话说,控制邮件服务器就意味着控制了公司的生命线。作为最常用的电子邮件解决方案,Exchange Server长期以来一直是黑客的首要目标。根据我们的研究,有超过四十万个Exchange Server暴露在互联网上;而每台服务器则代表着一家公司,因此,您可以想象Exchange server中出现严重漏洞时,到底有多可怕。

通常情况下,在开始研究之前,我会考察现有的论文和安全漏洞。纵观Exchange的历史,是否存在有趣的案例呢?当然有。虽然大多数漏洞都是基于已知的攻击手段,比如反序列化或糟糕的输入验证,但仍有几个漏洞让人眼前一亮。

最特殊的漏洞

这个最特别的漏洞,就是2017年被Equation Group曝光的arsenal漏洞。这是Exchange历史上唯一实用和公开的无需身份验证的RCE漏洞。不幸的是,arsenal漏洞仅适用于古老的Exchange Server 2003。如果泄漏时间再提前一些,arsenal很可能成为另一个核弹级漏洞。

最有趣的漏洞

最有趣的漏洞,是由与ZDI合作的研究人员披露的CVE-2018-8581。虽然它只是一个简单的SSRF漏洞,但有了这个特性,就可以与NTLM Relay结合起来,这样的话,攻击者便可以把一个普通的SSRF玩出花来。例如,利用这种方式,攻击者可以通过一个低权限账户直接接管整个域控制器。

最令人惊讶的漏洞

最令人惊讶的漏洞是CVE-2020-0688,也是由ZDI的工作人员披露的。这个漏洞的根本原因是由于Microsoft Exchange中的硬编码加密密钥造成的。使用这个硬编码的密钥,具有低权限的攻击者可以接管整个Exchange Server。正如你所看到的,即使在2020年,一个愚蠢的硬编码密码密钥仍然可以在像Exchange这样的重要软件中找到。这表明Exchange缺乏安全审查,这也让我对Exchange的安全性进行深入研究增加了更大的动力。

火狐截图_2021-08-08T05-46-02.682Z.png

新的攻击面在哪里

Exchange是一个非常复杂的应用程序。自2000年以来,Exchange每3年就会发布一个新的版本。每当Exchange发布一个新的版本,架构就会发生很大的变化。架构的变化和迭代使得Exchange服务器的升级变得困难。为了确保新架构和旧架构之间的兼容性,Exchange服务器出现了一些设计上的缺陷,从而导致了我们新发现的攻击面。

1.png

对于Exchange来说,我们的重点关注对象是什么?我们重点关注的是客户访问服务,即CAS。CAS是Exchange的一个基本组成部分。早在2000/2003年的版本中,CAS还是一个独立的前端服务器,负责所有的前端网络渲染逻辑。经过几次重命名、整合和版本更新,CAS已经被降级为Mailbox Role下的一个服务。根据微软的官方文档:    

  • 邮箱服务器包含接受所有协议的客户端连接的客户端访问服务。这些前端服务负责路由或代理连接到邮箱服务器上的相应后端服务。

从上面的叙述中你可以意识到CAS的重要性,也不难想象当在这种基础设施中发现漏洞时,会造成多大的危害。因此,CAS正是我们需要关注的地方,也是攻击面出现的地方。

CAS的架构

CAS是负责接受所有来自客户端的连接的基本组件,无论它是HTTP、POP3、IMAP还是SMTP,并代理连接到相应的后端服务。作为一名网络安全研究人员,我自然会专注于CAS的网络实现。

1.png

CAS的网络是建立在Microsoft IIS之上的。正如你所看到的,IIS里面有两个网站。“Default Website”是我们之前提到的前端,而“Exchange Backend”则是业务逻辑之所在。在仔细查看配置后,我们注意到前端与80和443端口相绑定,而后端则监听81和444端口。并且,所有的端口都与0.0.0.0绑定在一起,这意味着任何人都可以直接访问Exchange的前端和后端。这不是很危险吗?请记住这个问题,我们将在后面给出答案。

1.png

Exchange通过IIS模块实现前端和后端的逻辑。前端和后端有几个模块来完成不同的任务,如过滤、验证和记录。前端必须包含一个代理模块。代理模块从客户端接收HTTP请求,并添加一些内部设置,然后将请求转发给后端。至于后端,所有的应用程序都包括Rehydration模块,它负责解析前端的请求,填充客户端的信息,并继续处理业务逻辑。稍后我们将详细介绍代理模块和Rehydration模块的工作原理。

1.png

前端代理模块

代理模块用于根据当前的ApplicationPath选择一个处理程序来处理客户端的HTTP请求。例如,访问/EWS时,将使用EwsProxyRequestHandler;对于/OWA则触发OwaProxyRequestHandler。Exchange中的所有处理程序都继承了ProxyRequestHandler的类,并实现其核心逻辑,例如如何处理来自用户的HTTP请求,从后端代理到哪个URL,以及如何与后端同步信息。该类也是整个代理模块中最核心的部分,我们可以把ProxyRequestHandler分成3个部分:

1.png

前端请求部分

请求部分将解析来自客户端的HTTP请求,并确定哪些cookie和头部可以被代理到后端。前端和后端都是根据HTTP头部来同步信息和代理内部状态。因此,Exchange定义了一个黑名单,以避免一些内部头信息被误用。

HttpProxy\ProxyRequestHandler.cs

protected virtual boolShouldCopyHeaderToServerRequest(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());

}

 在处理请求的最后阶段,代理模块将调用处理程序实现的AddProtocolSpecificHeadersToServerRequest方法,在HTTP头部中添加要与后端通信的信息。这一部分还将序列化当前登录用户的信息,并将其放入一个新的HTTP头部X-CommonAccessToken中,并将其转发给后端。

例如,如果我用Orange这个名字登录Outlook Web Access(OWA),那么,前端代理发给后端的X-CommonAccessToken头部将是下面的样子:

1.png

前端代理部分

代理部分首先使用GetTargetBackendServerURL方法来计算HTTP请求应该被转发到哪个后端URL。然后,它会用CreateServerRequest方法初始化一个新的HTTP客户端请求。

HttpProxy\ProxyRequestHandler.cs

protected HttpWebRequestCreateServerRequest(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服务类生成一个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(stringhost, int

traceContext, ref AuthenticationContextauthenticationContext, 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头部供内部使用。其中,两个最基本的头部是X-CommonAccessToken,它表示邮件用户的登录身份;另一个头部是Kerberos Ticket,它代表来自前端的合法访问权限。

1.png

前端响应部分

最后是响应部分。它接收来自后端的响应,并决定哪些头部或cookies允许被送回前端。

后端Rehydration模块

现在,让我们继续检查后端如何处理来自前端的请求。后端首先使用IsAuthenticated方法来检查传入的请求是否通过了身份验证。然后,后端将验证该请求是否配备了名为ms-Exch-EPI-Token-Serialization的扩展权限。在默认设置下,只有Exchange机器账户会有这样的授权。这也是为什么前端生成的Kerberos票据可以通过检查点,但你却无法用低权限账户直接访问后端的原因。

通过检查后,Exchange将恢复前端使用的登录身份,通过反序列化头部X-CommonAccessToken,恢复原始访问令牌,然后,将其放入httpContext对象中,以进入后端的业务逻辑。

Authentication\BackendRehydrationModule.cs

private void OnAuthenticateRequest(objectsource, EventArgs args) {

   if (httpContext.Request.IsAuthenticated) {

       this.ProcessRequest(httpContext);

    }

}

 

private void ProcessRequest(HttpContexthttpContext) {

   CommonAccessToken token;

   if (this.TryGetCommonAccessToken(httpContext, out token)) {

       // ...

    }

}

 

private boolTryGetCommonAccessToken(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 asWindowsIdentity);

    }finally {

       httpContext.Items["BEValidateCATRightsLatency"] =

stopwatch.ElapsedMilliseconds -elapsedMilliseconds;

    }

 

   token = CommonAccessToken.Deserialize(text);

   httpContext.Items["Item-CommonAccessToken"] = token;

   

   //...

}

 

private boolIsTokenSerializationAllowed(WindowsIdentity

windowsIdentity) {

  flag2 = LocalServer.AllowsTokenSerializationBy

(clientSecurityContext);

  return flag2;

}

 

private static boolAllowsTokenSerializationBy(ClientSecurityContext

clientContext) {

   return LocalServer.HasExtendedRightOnServer(clientContext,

       WellKnownGuid.TokenSerializationRightGuid);  // ms-Exch-EPI-

Token-Serialization

 

}

 攻击面在简单介绍了CAS的架构后,我们现在已经意识到:CAS只不过是一个写得很好的HTTP代理(或者说客户端)而已;同时,我们都知道实现代理并不是一件容易的事情。所以,我在想: 

  • 我能不能用一个HTTP请求来分别访问前端和后端不同的上下文,以制造一些混乱?

如果我们能做到这一点,也许我可以绕过一些前端的限制,来访问任意的后端并滥用一些内部API。或者,我们可以通过混淆上下文,利用前端和后端之间危险的HTTP头部定义的不一致性,来发动进一步的攻击。

带着这些想法,让我们开始猎洞吧!

ProxyLogon

第一个漏洞是ProxyLogon。正如之前介绍的,这可能是Exchange历史上最严重的漏洞。ProxyLogon是通过两个漏洞组合而成:    

  • CVE-2021-26855:无需身份验证的SSRF漏洞导致身份验证绕过;    
  • CVE-2021-27065:需要经过身份验证的任意文件写入漏洞导致RCE;
  • CVE-2021-26855:无需身份验证的SSRF漏洞。

对于前端的不同应用路径,存在20多个相应的处理程序。在查看实现时,我们发现GetTargetBackEndServerUrl方法负责计算静态资源处理程序中的后端URL,它可以直接通过cookie分配后端目标。在学习了架构之后,您就明白前面为什么说这个漏洞其实非常简单了吧!

HttpProxy\ProxyRequestHandler.cs

protected virtual UriGetTargetBackEndServerUrl() {

   this.LogElapsedTime("E_TargetBEUrl");

   Uri result;

   try {

       UrlAnchorMailbox urlAnchorMailbox =

this.AnchoredRoutingTarget.AnchorMailbox asUrlAnchorMailbox;

       if (urlAnchorMailbox != null) {

           result = urlAnchorMailbox.Url;

       } else {

           UriBuilder clientUrlForProxy = this.GetClientUrlForProxy();

           clientUrlForProxy.Scheme = Uri.UriSchemeHttps;

           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;

}

从上面的代码片段中可以看到,AnchoredRoutingTarget的BackEndServer.Fqdn属性是由cookie直接分配的。

HttpProxy\OwaResourceProxyRequestHandler.cs

protected override AnchorMailboxResolveAnchorMailbox() {

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

       }

       return new ServerInfoAnchorMailbox(BackEndServer.FromString

(this.savedBackendServer), this);

    }

   return new AnonymousAnchorMailbox(this);

}

虽然我们现在只能控制URL的Host部分,但是等一下,操纵URL解析器不正是我所擅长的事情吗?不过,Exchange是通过内置的UriBuilder来建立后端URL的。然而,由于C#没有验证Host,所以,我们可以用一些特殊字符来围住整个URL,以访问任意的服务器和端口。

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

 1.png

到目前为止,我们已经找到了一个超级SSRF漏洞,可以控制几乎所有的HTTP请求,并获得所有的响应。最令人印象深刻的是,Exchange的前端会为我们生成一个Kerberos票据,这意味着即使我们攻击的是一个受保护的、与域相连的HTTP服务,我们仍然可以利用Exchange机器帐户的身份验证来发动攻击。

那么,这种任意的后端权限分配的根本原因是什么?如前所述,Exchange服务器在发布新版本时改变了其架构。它在不同的版本中可能有不同的功能,即使是同一名称下的同一组件也是如此。虽然微软为确保新旧版本之间的架构架构兼容方面付出了巨大努力,但是,这个cookie却只是一个快餐式的解决方案,Exchange的设计缺陷使得新架构中的前端可以识别旧的后端在哪里。

CVE-2021-27065:需要身份验证的任意文件写入漏洞

利用这个超级SSRF漏洞,我们已经可以不受限制地访问后端了。接下来要做的事情,就是找到一个RCE漏洞,并将其串联起来。在这里,我们利用后端内部的API/proxyLogon.ecp获得管理员权限。这个漏洞之所以命名为ProxyLogon,也是由于需要利用这个API的缘故。

因为我们利用静态资源的前端处理程序来访问ECExchange控制面板(ECP)后端,这样,前端就不会阻止头部msExchLogonMailbox了,因为它是ECP后端的一个特殊HTTP头部。通过利用这种轻微的不一致性,我们可以将自己指定为SYSTEM用户,并使用内部API生成有效的ECP会话。

1.png

利用前端和后端之间的不一致性,我们就可以通过伪造头部并滥用内部后端API来访问ECP上的所有功能。接下来,我们必须在ECP接口上找到一个RCE漏洞,并把它们串联一起。ECP通过/ecp/DDI/DDIService.svc把Exchange的PowerShell命令封装成一个抽象接口。DDIService通过XAML定义了几个PowerShell执行管道,使其可以通过Web访问。在检查DDI的实现代码时,我们发现WriteFileActivity的标签并没有正确地检查文件路径,从而导致了任意文件写入漏洞。

DDIService\WriteFileActivity.cs

public override RunResult Run(DataRow input,DataTable dataTable,

DataobjectStore store, Type codeBehind,Workflow.UpdateTableDelegate

updateTableDelegate) {

   DataRow dataRow = dataTable.Rows[0];

   string value = (string)input[this.InputVariable];

   string path = (string)input[this.OutputFileNameVariable];

   RunResult runResult = new RunResult();

   try {

       runResult.ErrorOccur = true;

       using (StreamWriter streamWriter = new StreamWriter(File.Open

(path, FileMode.CreateNew)))

       {

           streamWriter.WriteLine(value);

       }

       runResult.ErrorOccur = false;

    }

   

   // ...

}

实际上,存在多种途径,它们都可以触发任意文件写入的漏洞。这里,我们用ResetOABVirtualDirectory.xaml作为例子,把Set-OABVirtualDirectory的结果写到webroot上,使其成为我们的Webshell。

1.png

现在,我们找到了一个有效的、无需身份验证的RCE漏洞链。也就是说,现在一个未经身份验证的攻击者已经可以通过暴露的443端口在Microsoft Exchange Server上执行任意命令了。这里是我们的演示视频。

后记

作为这个系列的第一篇文章,ProxyLogon完美地展示了这个攻击面的危险程度。接下来,我们将演示更多的相关示例,敬请期待!

下一篇:MS Exchange的新攻击面,第2部分:ProxyOracle!

原文地址:https://devco.re/blog/2021/08/06/a-new-attack-surface-on-MS-exchange-part-1-ProxyLogon/

最新评论

昵称
邮箱
提交评论