Overview
The Seer challenge involves exploiting a cryptographic vulnerability known as the Padding Oracle Attack. This attack allows an adversary to decrypt data encrypted with block ciphers in Cipher Block Chaining (CBC) mode without knowing the encryption key. The attack takes advantage of the way padding is handled in CBC mode, specifically when using PKCS7 padding.
Challenge Description
In this challenge, we are given access to a remote service that encrypts data using AES in CBC mode. Our goal is to decrypt a given ciphertext by exploiting the padding oracle vulnerability.
Padding Oracle Attack Explanation
The Padding Oracle Attack is a type of side-channel attack that exploits the padding validation of a cryptographic message. When a message is encrypted using CBC mode, it is divided into blocks, and each block is XORed with the previous ciphertext block before being encrypted. The last block is padded to ensure it is the correct length.
If the decryption process reveals whether the padding is correct or not, an attacker can use this information to decrypt the message. By sending modified ciphertexts to the oracle and observing the responses, the attacker can deduce the plaintext.
Attack Steps
- Initialization: The ciphertext is divided into blocks of 16 bytes (the AES block size). The attack is performed block by block, starting from the last block.
- Brute Force: For each byte in the block, the attacker modifies the preceding block to guess the padding value. By iterating over all possible byte values (0-255), the attacker can determine the correct padding.
- Decryption: Once the correct padding is found, the attacker can deduce the plaintext byte by XORing the guessed byte with the corresponding byte in the previous ciphertext block and the padding value.
- Repeat: This process is repeated for each byte in the block, and then for each block in the ciphertext.
- Result: The attacker eventually recovers the entire plaintext, except for the first block, unless the Initialization Vector (IV) is known.
Implementation
The solution to this challenge is implemented in Python, as shown in the solve.py
script. The script connects to the remote service and performs the padding oracle attack to recover the plaintext.
Key Functions
padding_oracle_attack(ciphertext)
: This function implements the attack logic. It iterates over each block and each byte within the block, using the oracle to determine the correct padding and recover the plaintext.correctly_padded(p, ct)
: This function interacts with the remote service to check if a given ciphertext has valid padding.main()
: The main function initiates the attack by retrieving the encrypted token from the service and calling thepadding_oracle_attack
function.
Solve Script
from pwn import *
import binascii
from tqdm import tqdm
# Connect to the service
p = remote('kubenode.mctf.io', 30024)
#p = process('./chal.py', level="DEBUG")
#Based on the script provided here: https://github.com/flast101/padding-oracle-attack-explained
def padding_oracle_attack(ciphertext):
BYTE_NB = 16 # AES block size
block_number = len(ciphertext) // BYTE_NB
decrypted = bytearray()
total_bytes = block_number * BYTE_NB
with tqdm(total=total_bytes, desc="Decrypting", unit="byte") as pbar:
# Go through each block
for i in range(block_number, 0, -1):
current_encrypted_block = ciphertext[(i-1)*BYTE_NB:i*BYTE_NB]
if i == 1:
previous_encrypted_block = bytearray(16)
else:
previous_encrypted_block = ciphertext[(i-2)*BYTE_NB:(i-1)*BYTE_NB]
bruteforce_block = bytearray(previous_encrypted_block)
current_decrypted_block = bytearray(16)
padding = 0
# Go through each byte of the block
for j in range(BYTE_NB, 0, -1):
padding += 1
# Bruteforce byte value
for value in range(256):
bruteforce_block[j-1] = (bruteforce_block[j-1] + 1) % 256
joined_encrypted_block = bytes(bruteforce_block) + current_encrypted_block
# Ask the oracle
if correctly_padded(p, joined_encrypted_block):
current_decrypted_block[-padding] = bruteforce_block[-padding] ^ previous_encrypted_block[-padding] ^ padding
# Prepare newly found byte values
for k in range(1, padding+1):
bruteforce_block[-k] = padding+1 ^ current_decrypted_block[-k] ^ previous_encrypted_block[-k]
break
pbar.update(1) # Update the progress bar for each byte processed
decrypted = current_decrypted_block + decrypted
# Remove padding
return decrypted[:-decrypted[-1]]
def correctly_padded(p,ct):
p.sendlineafter(b'> ', b'2')
p.sendline(binascii.hexlify(ct))
response = p.recvline().decode()
return("fortune is as follows" in response or "your fortune seems corrupted." in response)
def main():
# Get the initial token from the service
p.sendlineafter(b'> ', b'1')
response = p.recvline().decode()
token_hex = response.split(',')[0].split()[-1]
token_bytes = binascii.unhexlify(token_hex)
# Perform the padding oracle attack
plaintext = padding_oracle_attack(token_bytes)[16:]
print(f"Recovered plaintext: {plaintext.decode(errors='ignore')}")
if __name__ == "__main__":
main()
Flag
MetaCTF{5t4r3_1nt0_7h3_ciph3r}
Credits
The explanation and implementation of the Padding Oracle Attack are based on the work by flast101. The solution script solve.py
is adapted from this explanation to solve the challenge.