Challenge example

Source code example

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import json
import os

FLAG = ?
KEY = ?

def admin():
    print("welcome admin")
    print(f"Flag = {FLAG}")


def guest():
    print("welcome guest")


def auth():
    user = dict()
    username = input("username : ")
    password = input("password : ")
    user["is_admin"] = int(username == "admin" and password == FLAG)
    
    plaintext = json.dumps(user).encode()
    padded = pad(plaintext, 16)
    iv = os.urandom(16)
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    try:
        encrypted = iv + cipher.encrypt(padded)
    except ValueError as e:
        return {"error": str(e)}

    print(f"Here is your token : {encrypted.hex()}")

    return user


def authToken():
    token = input("Token : ")
    token = bytes.fromhex(token)
    iv = token[:16]
    token = token[16:]

    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    try:
        plaintext = cipher.decrypt(token)
        unpadded = unpad(plaintext, 16)
        user = json.loads(unpadded)
    except ValueError as e:
        return {"error": str(e)}

    return user


print("Welcome !")
print("Please login first.")

choice = input("How would you login ?\n1) Token\n2) Credentials\n\nChoice :")

if choice == "1":
    user = authToken()
elif choice == "2":
    user = auth()
else:
    print("error... goodbye")
    exit(0)

if "error" in user:
    print(f"ERROR : {user['error']}")
    exit(0)

elif user["is_admin"] == 1:
    admin()
else:
    guest()

The challenge objective is to authenticate as admin using an arbitrary token forged using bit flipping attack.

Exploitation

First, a legit token is needed.

$ python3 chall.py
Welcome !
Please login first.
How would you login ?
1) Token
2) Credentials

Choice :2
username : a
password : a
Here is your token : 9cdc43fb9d2fed9a0b5ccc837d4a5badf1b4b1731bcca9977bc862f3e2033970
welcome guest

The token contains two blocks, the IV and the ciphertext block.

As explain here, plaintext is xored with the precedent cipherblock ( the IV for the first block ) before encryption. As we can control the ciphertext, we can control the plaintext.

The original plaintext is :

b'{"is_admin": 0}\x01'

The json data + padding

The targeted data is :

b'{"is_admin": 1}\x01'

The IV can be replace with the value 9cdc43fb9d2fed9a0b5ccc837d4b5bad obtaned using the following operation :

xor(iv, b'{"is_admin": 0}\x01', b'{"is_admin": 1}\x01')

And then using the new token, the admin access is granted :

$ python3 chall.py
Welcome !
Please login first.
How would you login ?
1) Token
2) Credentials

Choice :1
Token : 9cdc43fb9d2fed9a0b5ccc837d4b5badf1b4b1731bcca9977bc862f3e2033970
welcome admin
Flag = FLAG{CBCBitFlipping}

Exploitation Script

from pwn import *
import string

context.log_level = 'error'

def getToken(username, password):

    p = remote("127.0.0.1", 1337)
    p.recvuntil(b":")
    p.sendline(b"2")
    p.recvuntil(b": ")
    p.sendline(username.encode())
    p.recvuntil(b": ")
    p.sendline(password.encode())
    p.recvuntil(b": ")
    token = p.recvline()[:-1].decode()

    p.close()
    return token


def authToken(token):
    p = remote("127.0.0.1", 1337)
    p.recvuntil(b":")
    p.sendline(b"1")
    p.recvuntil(b": ")
    p.sendline(token.encode())

    print(p.clean().decode())

    p.close()

padding = 0
token = bytes.fromhex(getToken("",""))

iv = token[:16]
blocks = token[16:]

original = b'{"is_admin": 0}\x01'
target = b'{"is_admin": 1}\x01'

iv = xor(iv, original, target)

token = (iv + blocks).hex()

authToken(token)

Exercice

If you want to try this exploit by yourself, you can pull this docker image :

docker pull thectfrecipes/crypto:CBCBitFlipping

Deploy the image using the followed command :

docker run --name thectfrecipes_crypto_CBCBitFlipping -it --rm -d -p 1337:1337 thectfrecipes/crypto:CBCBitFlipping

The service is available on port 1337

nc 127.0.0.1 1337

Last updated