Source code example
Copy 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.
Copy $ 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 :
Copy b '{"is_admin": 0}\x01'
The targeted data is :
Copy b '{"is_admin": 1}\x01'
The IV can be replace with the value 9cdc43fb9d2fed9a0b5ccc837d4b5bad
obtaned using the following operation :
Copy xor (iv, b '{"is_admin": 0}\x01' , b '{"is_admin": 1}\x01' )
And then using the new token, the admin access is granted :
Copy $ 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
Copy 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 :
Copy docker pull thectfrecipes/crypto:CBCBitFlipping
Deploy the image using the followed command :
Copy docker run --name thectfrecipes_crypto_CBCBitFlipping -it --rm -d -p 1337:1337 thectfrecipes/crypto:CBCBitFlipping
The service is available on port 1337