Orange:MS Exchange 新攻击面第二部分 - ProxyOracle!
作者:Orange Tsai(@orange_8361)
嗨,这是新 MS Exchange 攻击面的第二部分。由于本文涉及前一篇文章中的几个架构介绍和攻击面概念,您可以在以下位置找到第一篇:
MS Exchange 新攻击面第一部分 - ProxyLogon!
这次,我们将介绍 ProxyOracle。与 ProxyLogon 相比,ProxyOracle 是一种采用不同方法的有趣漏洞利用。通过简单地引导用户访问恶意链接,ProxyOracle 允许攻击者完全以明文格式恢复用户的密码。ProxyOracle 包含两个漏洞:
CVE-2021-31195 - 反射型跨站脚本
CVE-2021-31196 - Exchange Cookie 解析中的填充 Oracle 攻击
ProxyOracle 的位置
那么 ProxyOracle 在哪里?基于我们之前介绍的 CAS 架构,CAS 的前端首先将用户身份序列化为字符串,并将其放入X-CommonAccessToken的标头中。该标头将合并到客户端的 HTTP 请求中,随后发送到后端。一旦后端收到,它会将标头反序列化为前端中的原始用户身份。
我们现在知道了前端和后端如何同步用户身份。接下来要解释的是前端如何知道您的身份并处理您的凭据。Outlook Web Access (OWA)使用一个花哨的界面来处理整个登录机制,称为基于表单的身份验证 (FBA)。FBA 是一个特殊的 IIS 模块,它继承自ProxyModule,并负责在进入代理逻辑之前执行凭据和 Cookie 之间的转换。
FBA 机制
HTTP 是一种无状态协议。为了保持您的登录状态,FBA 将用户名和密码保存在 Cookie 中。每次您访问 OWA 时,Exchange 将解析 Cookie,检索凭据并尝试使用该凭据登录。如果登录成功,Exchange 会将您的用户身份序列化为字符串,将其放入X-CommonAccessToken的标头中,并将其转发到后端。
HttpProxy\FbaModule.cs
protected override void OnBeginRequestInternal(HttpApplication httpApplication) { httpApplication.Context.Items["AuthType"] = "FBA"; if (!this.HandleFbaAuthFormPost(httpApplication)) { try { this.ParseCadataCookies(httpApplication); } catch (MissingSslCertificateException) { NameValueCollection nameValueCollection = new NameValueCollection(); nameValueCollection.Add("CafeError", ErrorFE.FEErrorCodes.SSLCertificateProblem.ToString()); throw new HttpException(302, AspNetHelper.GetCafeErrorPageRedirectUrl(httpApplication.Context, nameValueCollection)); } } base.OnBeginRequestInternal(httpApplication);}
复制代码
所有 Cookie 都被加密,以确保即使攻击者能够劫持 HTTP 请求,他/她也无法以明文格式获取您的凭据。FBA 利用 5 个特殊的 Cookie 来完成整个加解密过程:
cadata - 加密的用户名和密码
cadataTTL - 生存时间戳
cadataKey - 加密的 KEY
cadataIV - 加密的 IV
cadataSig - 防止篡改的签名
加密逻辑首先生成两个 16 字节的随机字符串作为当前会话的 IV 和 KEY。用户名和密码随后使用 Base64 编码,通过 AES 算法加密,并在 Cookie 中发送回客户端。同时,IV 和 KEY 也会发送给用户。为了防止客户端直接通过已知的 IV 和 KEY 解密凭据,Exchange 在发送出去之前会再次使用其 SSL 证书私钥通过 RSA 算法加密 IV 和 KEY!
以下是加密逻辑的伪代码:
@key = GetServerSSLCert().GetPrivateKey()cadataSig = RSA(@key).Encrypt("Fba Rocks!")cadataIV = RSA(@key).Encrypt(GetRandomBytes(16))cadataKey = RSA(@key).Encrypt(GetRandomBytes(16))
@timestamp = GetCurrentTimestamp()cadataTTL = AES_CBC(cadataKey, cadataIV).Encrypt(@timestamp)
@blob = "Basic " + ToBase64String(UserName + ":" + Password)cadata = AES_CBC(cadataKey, cadataIV).Encrypt(@blob)
复制代码
Exchange 采用 CBC 作为其填充模式。如果您熟悉密码学,您可能想知道这里的 CBC 模式是否容易受到填充 Oracle 攻击?没错!事实上,在 2021 年,像 Exchange 这样重要的软件中仍然存在填充 Oracle 攻击!
CVE-2021-31196 - 填充 Oracle
当 FBA 出现问题时,Exchange 会附加一个错误代码并将 HTTP 请求重定向回原始登录页面。那么 Oracle 在哪里?在 Cookie 解密中,Exchange 使用异常来捕获填充错误,并且由于异常,程序立即返回,因此错误代码号为0,表示None:
Location: /OWA/logon.aspx?url=…&reason=0
复制代码
与填充错误相反,如果解密成功,Exchange 将继续认证过程,并尝试使用损坏的用户名和密码登录。此时,结果必然是失败,错误代码号为2,表示InvalidCredentials:
Location: /OWA/logon.aspx?url=…&reason=2
复制代码
图表如下:
[此处应有图表]
有了这个差异,我们现在有了一个 Oracle 来识别解密过程是否成功。
HttpProxy\FbaModule.cs
private void ParseCadataCookies(HttpApplication httpApplication){ HttpContext context = httpApplication.Context; HttpRequest request = context.Request; HttpResponse response = context.Response; string text = request.Cookies["cadata"].Value; string text2 = request.Cookies["cadataKey"].Value; string text3 = request.Cookies["cadataIV"].Value; string text4 = request.Cookies["cadataSig"].Value; string text5 = request.Cookies["cadataTTL"].Value; // ... RSACryptoServiceProvider rsacryptoServiceProvider = (x509Certificate.PrivateKey as RSACryptoServiceProvider); byte[] array = null; byte[] array2 = null; byte[] rgb2 = Convert.FromBase64String(text2); byte[] rgb3 = Convert.FromBase64String(text3); array = rsacryptoServiceProvider.Decrypt(rgb2, true); array2 = rsacryptoServiceProvider.Decrypt(rgb3, true); // ... using (AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider()) { aesCryptoServiceProvider.Key = array; aesCryptoServiceProvider.IV = array2; using (ICryptoTransform cryptoTransform2 = aesCryptoServiceProvider.CreateDecryptor()) { byte[] bytes2 = null; try { byte[] array5 = Convert.FromBase64String(text); bytes2 = cryptoTransform2.TransformFinalBlock(array5, 0, array5.Length); } catch (CryptographicException ex8) { if (ExTraceGlobals.VerboseTracer.IsTraceEnabled(1)) { ExTraceGlobals.VerboseTracer.TraceDebug<CryptographicException>((long)this.GetHashCode(), "[FbaModule::ParseCadataCookies] Received CryptographicException {0} transforming auth", ex8); } httpApplication.Response.AppendToLog("&CryptoError=PossibleSSLCertrolloverMismatch"); return; } catch (FormatException ex9) { if (ExTraceGlobals.VerboseTracer.IsTraceEnabled(1)) { ExTraceGlobals.VerboseTracer.TraceDebug<FormatException>((long)this.GetHashCode(), "[FbaModule::ParseCadataCookies] Received FormatException {0} decoding caData auth", ex9); } httpApplication.Response.AppendToLog("&DecodeError=InvalidCaDataAuthCookie"); return; } string @string = Encoding.Unicode.GetString(bytes2); request.Headers["Authorization"] = @string; } }}
复制代码
需要注意的是,由于 IV 是使用 SSL 证书私钥加密的,我们无法通过 XOR 恢复密文的第一个块。但这不会给我们带来任何问题,因为 C#在内部将字符串处理为 UTF-16,所以密文的前 12 个字节必须是B\x00a\x00s\x00i\x00c\x00 \x00。再加上应用了一次 Base64 编码,我们只会在用户名字段中丢失前 1.5 个字节。
(16−6×2) ÷ 2 × (3/4) = 1.5
复制代码
漏洞利用
到目前为止,我们有一个填充 Oracle,允许我们解密任何用户的 Cookie。但是,我们如何获取客户端 Cookie 呢?在这里,我们发现了另一个漏洞将它们链接在一起。
XSS 窃取客户端 Cookie
我们在 CAS 前端(是的,又是 CAS)中发现了一个 XSS(CVE-2021-31195)来链接在一起,这个 XSS 的根本原因相对简单:Exchange 在打印数据之前忘记清理数据,因此我们可以使用\从 JSON 格式中转义并注入任意 JavaScript 代码。
https://exchange/owa/auth/frowny.aspx?app=people&et=ServerError&esrc=MasterPage&te=\&refurl=}}};alert(document.domain)//
复制代码
但这里又出现了另一个问题:所有敏感 Cookie 都受 HttpOnly 标志保护,这使我们无法通过 JavaScript 访问 Cookie。我们该怎么办?
绕过 HttpOnly
由于我们可以在浏览器上执行任意 JavaScript,为什么不直接插入我们在 ProxyLogon 中使用的 SSRF Cookie 呢?一旦我们添加了这个 Cookie 并将后端目标值分配为我们的恶意服务器,Exchange 将成为受害者和我们之间的代理。然后,我们可以接管所有客户端的 HTTP 静态资源并获取受保护的 HttpOnly Cookie!
通过将漏洞链接在一起,我们有了一个优雅的漏洞利用,只需向用户发送恶意链接即可窃取任何用户的 Cookie。值得注意的是,这里的 XSS 仅帮助我们窃取 Cookie,这意味着所有解密过程都不需要任何认证和用户交互。即使用户关闭浏览器,也不会影响我们的填充 Oracle 攻击!
以下是演示我们如何恢复受害者密码的演示视频:
[此处应有演示视频]更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)
公众号二维码
办公AI智能小助手
公众号二维码
网络安全技术点滴分享
评论