Orange:微软Exchange新攻击面第二部分 - ProxyOracle!
作者:Orange Tsai(@orange_8361)
嗨,这是新MS Exchange攻击面的第二部分。由于本文涉及前一篇文章中的几个架构介绍和攻击面概念,您可以在以下位置找到第一篇:
《微软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头中,并将其转发到后端。
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 - 加密的KEY
- cadataIV - 加密的IV
- 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模式是否容易受到填充Oracle攻击?没错!事实上,在2021年,像Exchange这样重要的软件中仍然存在填充Oracle攻击!
CVE-2021-31196 - 填充Oracle
当FBA出现问题时,Exchange会附加一个错误代码并将HTTP请求重定向回原始登录页面。那么Oracle在哪里?在cookie解密中,Exchange使用异常来捕获填充错误,并且由于异常,程序立即返回,因此错误代码号为0,表示无错误:
1
|
Location: /OWA/logon.aspx?url=…&reason=0
|
与填充错误相反,如果解密良好,Exchange将继续身份验证过程,并尝试使用损坏的用户名和密码登录。此时,结果必然是失败,错误代码号为2,表示InvalidCredentials:
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个字节。
(16−6×2) ÷ 2 × (3/4) = 1.5
漏洞利用
到目前为止,我们有一个填充Oracle,允许我们解密任何用户的cookie。但是,我们如何获取客户端cookie?在这里,我们发现了另一个漏洞将它们链接在一起。
XSS窃取客户端Cookie
我们在CAS前端(是的,又是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,这意味着所有解密过程都不需要任何身份验证和用户交互。即使用户关闭浏览器,也不会影响我们的填充Oracle攻击!
以下是演示我们如何恢复受害者密码的演示视频: