What the Flip?!

AES CBC Byte Flipping Attack

Problem

Hackers have locked you out of your account! Fortunately their netcat server has a vulnerability.

nc umbccd.io 3000

This netcat server is username and password protected. The admin login is known but forbidden. Any other login entered gives a cipher.

Author: Clearedge

Solution

We can input the user and passwd into the below string, which is encrypted. admin&password=goBigDawgs12 is not allowed.

msg = 'logged_username=' + user +'&password=' + passwd

try:
    assert('admin&password=goBigDawgs123' not in msg)
except AssertionError:
    send_msg(s, 'You cannot login as an admin from an external IP.\nYour activity has been logged. Goodbye!\n')
    raise

The ciphertext is given to us, and we are prompted to enter another ciphertext.

send_msg(s, "Leaked ciphertext: " + encrypt_data(msg)+'\n')
send_msg(s,"enter ciphertext: ")

Then, in decrypt_data(), the presense of admin&password=goBigDawgs12 is checked. The goal is to submit a ciphertext such that the corresponding plaintext contains admin&password=goBigDawgs12.

def decrypt_data(encryptedParams):
    cipher = AES.new(key, AES.MODE_CBC,iv)
    paddedParams = cipher.decrypt(unhexlify(encryptedParams))
    print(paddedParams)
    if b'admin&password=goBigDawgs123' in unpad(paddedParams,16,style='pkcs7'):
        return 1
    else:
        return 0

From encrypt_data(), we can see that AES CBC encryption is used, with a block size of 16.

def encrypt_data(data):
    padded = pad(data.encode(),16,style='pkcs7')
    cipher = AES.new(key, AES.MODE_CBC,iv)
    enc = cipher.encrypt(padded)
    return enc.hex()

Since we are given a ciphertext and tasked to find another ciphertext that decodes into a different string, an AES CBC byte flipping attack can be used.

In CBC, each block of plaintext depends on the previous ciphertext.

So if we manage to change the ciphertext in a previous block, we can change the plaintext in the next block.

We can send a payload like logged_username=admin&parsword=goBigDawgs123 (note the purposeful misspelling of password as parsword). Then, we will edit the previous block of ciphertext such that r becomes s at the misspelt index. We simply have to change the ciphertext at the correct index.

For instance, the following code gives us the edited ciphertext.

user = ''
password = 'goBigDawgs123'
msg = 'logged_username=admin&parsword=goBigDawgs123' + user +'&password=' + password
print(msg, len(msg))

xor = ord('r') ^ ord('s')
cipher = encrypt_data(msg)
cipher = cipher[:16] + hex(int(cipher[16:18], 16) ^ xor)[2:] + cipher[18:]
print(decrypt_data(cipher))

Notice that the second block has been changed to the desired string. Since we modified the first block, it will no longer decode properly (but in this case it doesn't matter since only the desired string is checked).

b'X\xd7\xfc;\xb4\x7fUc\x14\xbe\xbfJ\x15\xe8}1admin&password=goBigDawgs123&password=goBigDawgs123\r\r\r\r\r\r\r\r\r\r\r\r\r'

Putting it all together:

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from Crypto.Util.number import bytes_to_long
from Crypto.Random import get_random_bytes
from binascii import unhexlify
from pwn import *
import re

key = get_random_bytes(16)
iv = get_random_bytes(16)

def encrypt_data(data):
    padded = pad(data.encode(),16,style='pkcs7')
    cipher = AES.new(key, AES.MODE_CBC,iv)
    enc = cipher.encrypt(padded)
    return enc.hex()

def decrypt_data(encryptedParams):
    cipher = AES.new(key, AES.MODE_CBC,iv)
    paddedParams = cipher.decrypt( unhexlify(encryptedParams))
    print(paddedParams)
    if b'admin&password=goBigDawgs123' in unpad(paddedParams,16,style='pkcs7'):
        return 1
    else:
        return 0

user = ''
password = 'goBigDawgs123'
msg = 'logged_username=admin&parsword=goBigDawgs123' + user +'&password=' + password
print(msg, len(msg))

xor = ord('r') ^ ord('s')
cipher = encrypt_data(msg)
cipher = cipher[:16] + hex(int(cipher[16:18], 16) ^ xor)[2:] + cipher[18:]
print(decrypt_data(cipher))

conn = remote('umbccd.io', 3000)

print(conn.recv())
print(conn.recv())
conn.send('admin&parsword=goBigDawgs123\r\n')
print(conn.recv())
conn.send('\r\n')

match = re.match(r'Leaked ciphertext: (.+)\n', conn.recv().decode())
print('Ciphertext:', match[1])

cipher = match[1]
cipher = cipher[:16] + hex(int(cipher[16:18], 16) ^ xor)[2:] + cipher[18:]
print('Modified Ciphertext', cipher)

print()
conn.send(cipher + '\r\n')
print(conn.recv())

conn.close()

Last updated