密码侧信道分析
0x01 前言
紧接着上一篇文章exchange获取cookie
,通过SSRF与XSS的配合得到了用户的cookie。 cookie中的cadata值就是用户名跟密码经过加密后得到的一串字符串。所以本次任务我们就是通过侧信道分析解密用户名跟密码。
0x11 CBC加密模式
首先我们要了解什么是CBC模式,CBC模式又称为密码分组链接模式(Cipher Block Chaining)。首先将明文分块,分成等长的明文模块。另外还有一个初始化向量IV,IV一般是随机生成。首先将第一块明文与初始化向量IV进行依次异或,然后将异或的结果(initialization value)进行加密得到我们的第一组密文,紧接着进行第二组密文的加密,此时将第一组密文作为第二次加密的IV进行异或以此类推进行剩下分组的加密。
0x21利用点
看样子CBC每组密文都是互相影响,安全性肯定很高才对,但是这个模式是存在一定的缺陷的,这个缺陷就会导致padding oracle攻击。因为明文加密之前要与IV进行异或,所以每一组IV是与分组明文等长的,那不可能待加密的明文长度都正好是某个数的倍数。所以当明文长度不足时要进行填充,例如加密的分组为8个字节,当不足8个字节时会进行填充,填充的规则就是少几个字节就填充多少个缺失的字节数,例如缺少3个字节就填充3个0x03,从而确保了是8的倍数。所以利用点就在填充上,首先解密的时候会先检查填充是否正确,如果不正确则解密失败,如果填充位错误的时候有回显就是造成padding oracle攻击。
CBC的解密过程如下,首先密文经过加密算法解密以后与initialization vector(IV)进行异或的得到明文。
那怎么利用,例如我们首先将初始化向量IV,定义成如下:
(initialization value:是明文与IV异或后的结果)
initialization value | 0x21 | 0x31 | 0x32 | 0x99 | 0x28 | 0x22 | 0x20 | 0x29 |
---|---|---|---|---|---|---|---|---|
initialization vector(IV) | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 |
PlainText | 0x21 | 0x31 | 0x32 | 0x99 | 0x28 | 0x22 | 0x20 | 0x29 |
那么经过异或之后0x29还是它本身,那这时服务端就会报填充位错误,因为填充位不可能超过0x08(ps:因为即使是明文刚刚好是倍数的时候也会有填充,例如填充一个新的模块即填充8个0x08),此时服务器就会报填充错误。那怎样才不会报填充错误当结果为如下:
initialization value | 0x21 | 0x31 | 0x32 | 0x99 | 0x28 | 0x22 | 0x20 | 0x29 |
---|---|---|---|---|---|---|---|---|
initialization vector(IV) | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x28 |
PlainText | 0x21 | 0x31 | 0x32 | 0x99 | 0x28 | 0x22 | 0x20 | 0x01 |
则不会报填充错误。当结果为0x01的时候,那IV我们是知道的,那么不就可以推出initialization value的最后一位了吗。所以经过不断变换使得PlainText
0x21 0x31 0x32 0x99 0x28 0x22 0x02 0x02
0x21 0x31 0x32 0x99 0x28 0x03 0x03 0x03
…
0x08 0x08 0x08 0x08 0x08 0x08 0x08 0x08
我们就可以推出完整的initialization value,而initialization value是明文与IV异或后的结果,而IV我们是知道的我们再把initialization value与IV异或不就可以得到PlainText了吗!
0x31漏洞复现
在Cookie中,有这么几个值,cadata:用户名与密码的密文,cadataKey:加密的密钥,cadataIV:加密所使用的IV,而cadataIV是使用SSL证书作为私钥使用RSA加密过的,所以我们无法破解。
我们看看加密流程
@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)
看到加密的流程即使IV不可控那顶多就是第一个模块解密不出,但是我们可以知道第一个模块的前12个字节
B\x00a\x00s\x00i\x00c\x00 \x00为什么是12个字节因为C#里面会将字符串当作UTF-16。而我们丢失的仅仅是四个字节,即用户名的1.5个字节。
当我们填充位错误时候返回头
Location: /OWA/logon.aspx?url=…&reason=0
当填充位正确,而拿着解密错的用户名跟密码进行后续的验证时候返回头有
Location: /OWA/logon.aspx?url=…&reason=2
这就是提示错误!
按照上述解密步骤一步一步的破译出全部分组,会得到一串base64编码,这时直接拿去解密要么解密不出要么解密结果出错,前文提到了我们丢失了四个字节,因为base64加密规则是将3个字节编为4个字节,所以我们要补上丢失的四个字节,这四个字节可以随意补上(4个0x00除外)。这时候拿去base64解密会发现用户名丢失两个字符,密码正确。