MS Exchange新型攻击面分析第二弹 - ProxyOracle密码窃取攻击

本文深入分析了MS Exchange服务器中被称为ProxyOracle的新型攻击链,通过CVE-2021-31195 XSS漏洞和CVE-2021-31196 Padding Oracle攻击的组合利用,攻击者仅需诱使用户点击恶意链接即可完全恢复用户明文密码。

Orange: MS Exchange新型攻击面分析第二弹 - ProxyOracle!

Orange发言 :)

2021年8月6日 星期五

MS Exchange新型攻击面分析第二弹 - ProxyOracle!

作者: Orange Tsai(@orange_8361)

大家好,这是MS Exchange新型攻击面分析系列的第二部分。由于本文涉及前文中的架构介绍和攻击面概念,您可以在以下链接找到第一部分内容:

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

本次我们将介绍ProxyOracle。与ProxyLogon相比,ProxyOracle采用了一种不同的有趣利用方式。通过简单诱导用户访问恶意链接,ProxyOracle允许攻击者完全恢复用户的明文密码。ProxyOracle包含两个漏洞:

  • CVE-2021-31195 - 反射型跨站脚本攻击
  • CVE-2021-31196 - Exchange Cookie解析中的Padding Oracle攻击

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头部,并转发到后端。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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 - 加密密钥
  • cadataIV - 加密初始化向量
  • cadataSig - 防篡改签名

加密逻辑首先生成两个16字节的随机字符串作为当前会话的IV和KEY。用户名和密码经过Base64编码,通过AES算法加密后随Cookie发送回客户端。同时,IV和KEY也会发送给用户。为防止客户端直接使用已知的IV和KEY解密凭据,Exchange在发送前会再次使用其SSL证书私钥通过RSA算法加密IV和KEY!

以下是加密逻辑的伪代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@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模式是否容易受到Padding Oracle攻击?没错!事实上,Padding Oracle攻击在2021年仍然存在于像Exchange这样的重要软件中!

CVE-2021-31196 - Padding Oracle漏洞

当FBA出现问题时,Exchange会附加错误代码并将HTTP请求重定向回原始登录页面。那么Oracle在哪里?在Cookie解密过程中,Exchange使用异常来捕获填充错误,由于异常,程序立即返回,因此错误代码为0,表示无错误:

1
Location: /OWA/logon.aspx?url=…&reason=0

相比之下,如果解密成功,Exchange将继续身份验证过程并尝试使用损坏的用户名和密码登录。此时,结果必然是失败,错误代码为2,表示无效凭据:

1
Location: /OWA/logon.aspx?url=…&reason=2

示意图如下:通过这种差异,我们现在有了一个Oracle来识别解密过程是否成功。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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字节。

1
(16−6×2) ÷ 2 × (3/4) = 1.5

利用过程

至此,我们有了一个可以解密任何用户Cookie的Padding Oracle。但是,如何获取客户端Cookie呢?这里我们发现了另一个漏洞可以将它们串联起来。

XSS窃取客户端Cookie

我们在CAS前端发现了一个XSS(CVE-2021-31195)漏洞进行串联,这个XSS的根本原因相对简单:Exchange在打印数据前忘记进行清理,使得我们可以使用\从JSON格式中转义并注入任意JavaScript代码。

1
2
3
4
5
6
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,这意味着所有解密过程都不需要任何身份验证和用户交互。即使用户关闭浏览器,也不会影响我们的Padding Oracle攻击!

以下是演示如何恢复受害者密码的演示视频:

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计