MS Exchange的新攻击面,第2部分:ProxyOracle!
大家好,这是本系列文章的第2部分。由于本文内容涉及前一篇文章中介绍的架构和攻击面方面的基础知识,所以,如果您还没有阅读过上一篇文章的话,建议先读一下:
- MS Exchange的新攻击面,第1部分:ProxyLogon!
这一次,我们将介绍ProxyOracle漏洞。与ProxyLogon漏洞相比,ProxyOracle是一个更加有趣的漏洞:只需引诱用户访问一个恶意链接,攻击者就能利用ProxyOracle漏洞恢复用户的密码。实际上,ProxyOracle是由两个漏洞组合而成的:
- CVE-2021-31195:反射型跨站脚本漏洞;
- CVE-2021-31196:针对Exchange Cookie解析的Padding Oracle攻击。
ProxyOracle漏洞出现在哪里
那么,Proxyoracle漏洞到底在哪里呢?根据之前对于CAS体系结构的介绍,我们知道CAS前端会先将用户标识序列化为一个字符串,并将其放入X-CommonAccessStoken的头部。之后,该头部将合并到客户端的HTTP请求中,然后将其发送到后端。一旦后端接收到,它就将该头部反序列化为前端的原始用户标识。
好了,我们已经知道前端和后端是如何同步用户标识的了。接下来要解释前端是如何知道确定用户身份,以及如何处理用户的凭据的。Outlook Web Access(OWA)使用一种奇特的接口来处理整个登录机制,这种机制称为基于表单的身份验证(FBA)。FBA是一个特殊的IIS模块,它继承自ProxyModule,并负责在进入代理逻辑之前处理凭据和cookie之间的转换。
FBA机制
HTTP是一种无状态协议。为了保持您的登录状态,FBA会将您的用户名和密码保存在cookies中。每当您访问OWA时,Exchange都会解析cookie,检索凭证并尝试用它来登录。如果登录成功,Exchange会把您的用户身份序列化为一个字符串,并把它放到X-CommonAccessToken的头部中,然后,将其转发给后端。
HttpProxy\FbaModule.cs
protected override voidOnBeginRequestInternal(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);
}
实际上,所有的cookies都是经过加密处理的,以确保即使攻击者能够劫持HTTP请求,他/她仍然无法得到您的明文格式的凭证。另外,FBA是通过5个特殊的cookies来完成整个加解密过程的。
- cadata:存放加密的用户名和密码;
- cadataTTL:存放生存时间戳;
- cadataKey:存放加密KEY;
- cadataIV:存放加密的IV;
- cadataSig:存放用于防止篡改的签名。
加密逻辑首先生成两个16字节的随机字符串作为当前会话的IV和KEY。然后,对用户名和密码进行base64编码,然后,用AES算法进行加密,并通过cookie发回给客户端。同时,IV和KEY也将被发送至用户。为了防止客户端通过已知的IV和KEY直接解密凭证,Exchange将再次使用RSA算法,在发送前通过其SSL证书私钥对IV和KEY进行加密。
下面给出加密逻辑的伪代码:
@key= GetServerSSLCert().GetPrivateKey()
cadataSig = RSA(@key).Encrypt("FbaRocks!")
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攻击的影响了。是的,虽然现在已经2021年了,但是Padding Oracle漏洞仍然存在于像Exchange这样的重要软件中!
CVE-2021-31196:Padding Oracle漏洞
当FBA出现问题时,Exchange会附加一个错误代码,并将HTTP请求重定向到原始登录页面。
那么,Oracle位于哪里呢?在cookie解密过程中,Exchange使用了一个异常来捕捉Padding Error;因为有了这个异常,程序会立即返回,所以,错误代码号是0,也就是None。
Location:/OWA/logon.aspx?url=…&reason=0
与Padding Error相反,如果解密过程顺利的话,Exchange将继续进行身份验证过程,并尝试用被篡改的用户名和密码登录。此刻,尝试的结果一定是失败,错误代码为2,代表InvalidCredntials的意思。
Location: /OWA/logon.aspx?url=…&reason=2
这个过程如下图所示:
知道了这个区别,我们就可以通过Oracle来识别解密过程是否成功。
HttpProxy\FbaModule.cs
private voidParseCadataCookies(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 = newAesCryptoServiceProvider()) {
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 (CryptographicExceptionex8) {
if(ExTraceGlobals.VerboseTracer.IsTraceEnabled(1)) {
ExTraceGlobals.VerboseTracer.TraceDebug<CryptographicException>((long)this.GetHashCode(),
"[FbaModule::ParseCadataCookies]Received CryptographicException
需要注意的是,由于IV是用SSL证书的私钥进行加密的,所以我们不能通过XOR操作来恢复密码文本的第一个块。但这并不会给我们带来任何问题,因为C#在内部以UTF-16的形式处理字符串,所以密码文本的前12个字节必定是B\x00a\x00s\x00i\x00c\x00\x00。然后,再进行base64编码,这样的话,实际上我们只会丢失username字段中的前1.5个字节。
(16−6×2) ÷ 2 × (3/4) = 1.5
漏洞利用方法
(16−6×2) ÷ 2 × (3/4) = 1.5
到目前为止,我们找到了一个Padding Oracle漏洞,通过它,我们就能够解密任何用户的cookie。但是,我们怎样才能得到客户端的cookie呢?在这里,我们找到了另一个漏洞,这样的话,就可以把它们组合在一起使用了。
用于窃取客户端Cookie的XSS漏洞
实际上,我们又在CAS前端(对,又是CAS)发现了一个XSS(CVE-2021-31195),这样我们就可以将这两个漏洞组合起来使用了;同时,这个XSS漏洞的成因相对来说比较简单:
Exchange在打印数据之前,忘记了对其进行相应的过滤,使得我们可以通过字符\对JSON格式进行转义,从而注入任意的ja vasc ript代码。
https://exchange/owa/auth/frowny.aspx
?app=people
&et=ServerError
&esrc=MasterPage
&te=\
&refurl=}}};alert(document.domain)//
但另一个问题又出现了:所有敏感的cookies都受到HttpOnly标志的保护,这使得我们无法通过ja vasc ript访问这些cookie。那么,我们应该如何应对呢?
绕过HttpOnly
既然我们可以在浏览器上执行任意的ja vasc ript,为什么不直接插入用于ProxyLogon的SSRF cookie呢?一旦我们添加了这个cookie并将后端的目标值指定为我们的恶意服务器,Exchange就会变成受害者和我们之间的代理。然后,我们就可以接管所有客户端的HTTP静态资源,并获得受保护的HttpOnly cookie了!
通过将这些漏洞组合起来,攻击者就能完成漂亮的一击:只要向用户发送一个恶意链接,就可以盗取任何用户的cookie。值得注意的是,这里的XSS只是帮助我们窃取cookie,这意味着所有的解密过程都不需要任何身份验证和用户互动。也就是说,即使用户关闭了浏览器,也不会影响我们的Padding Oracle攻击!
下面是演示视频,展示了我们是如何恢复受害者的密码的:
https://www.youtube.com/watch?v=VuJvmJZxogc
原文地址:https://devco.re/blog/2021/08/06/a-new-attack-surface-on-MS-exchange-part-2-ProxyOracle/
最新评论