Code source example
Copy from Crypto . Cipher import AES
from Crypto . Util . Padding import pad , unpad
import json
FLAG = ?
KEY = ?
def admin ():
print ( "welcome admin" )
print ( f "Flag = { FLAG } " )
def guest ():
print ( "welcome guest" )
def auth ():
user = dict ()
user [ "username" ] = input ( "username : " )
user [ "password" ] = input ( "password : " )
user [ "uid" ] = int (user[ "username" ] == "admin" and user[ "password" ] == FLAG)
plaintext = json . dumps (user). encode ()
padded = pad (plaintext, 16 )
cipher = AES . new (KEY, AES.MODE_ECB)
try :
encrypted = 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)
cipher = AES . new (KEY, AES.MODE_ECB)
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 [ "uid" ] == 1 :
admin ()
else :
guest ()
The challenge objectif is to log as admin user.
Exploitation
When the user authenticates using their credentials, the application will send a token that can be reuse to directly access the app.
The token contain :
username
: provided by the user
password
: provided by the user
uid
: set by the application depending if the provided credentials are admin or not.
Can the user forge an arbitrary block ?
Copy $ python3 -c 'print("2\n" + "A"*2 + "A"*16*2 + "\na\n")' | python3 chall.py
# Sending choice 2 to authenticate using credentials
# Sending as username : 2 bytes to complete the first block + 16 bytes * 2 to check if the block are correctly forged ( second and third block may be the same )
# Sending anything (a) as password... this pars isn't needed to exploit.
Welcome !
Please login first.
How would you login ?
1 ) Token
2 ) Credentials
Choice :username : password : Here is your token : 0f0db6ff7eb32259e2ab26faad5bea05eb159765773a70532da4789b0305a592eb159765773a70532da4789b0305a59265c235f39040a271d8e612fcb3cf0e3863e9de070bb91f9a2714f8b733371020
welcome guest
# Checking Token :
>>> result[32:64]
'eb159765773a70532da4789b0305a592'
>>> result[64:96]
'eb159765773a70532da4789b0305a592'
The user can effectively forge arbitrary blocks.
In order to obtain admin access the user need to replace the block containing 'uid': 0
Here we will manipulate the input to have this at the end of the penultimate block :
Copy +------------------+------------------+------------------+--------------+
| {'username': "AA | ", 'password': ' | AAAAAA', 'uid': | 0} + padding |
+------------------+------------------+------------------+--------------+
Copy $ python3 -c 'print("2\n" + "A"*2 + "\n" + "A"*6 + "\n")' | python3 chall.py
Welcome !
Please login first.
How would you login ?
1 ) Token
2 ) Credentials
Choice :username : password : Here is your token : 0f0db6ff7eb32259e2ab26faad5bea05c5f31a2e59a38743077acd22e4512d69eb6c532e83118ae9a89f176933e2cd0f7e8c582ca3535bc548f256f69832be4f
welcome guest
We now need to forge the block 1
Copy $ python3 -c 'print("2\n" + "A"*2 + " "*15 + "1" + "\n" + "A"*6 + "\n")' | python3 chall.py
Welcome !
Please login first.
How would you login ?
1 ) Token
2 ) Credentials
Choice :username : password : Here is your token : 0f0db6ff7eb32259e2ab26faad5bea050ebd6b5fd3363bc885c982fd44c1e7f3c5f31a2e59a38743077acd22e4512d69eb6c532e83118ae9a89f176933e2cd0f7e8c582ca3535bc548f256f69832be4f
welcome guest
We need a last block :
Copy # } + padding
$ python3 -c 'print("2\n" + "A"*2 + "\n" + "A"*5 + "\n")' | python3 chall.py
Welcome !
Please login first.
How would you login ?
1 ) Token
2 ) Credentials
Choice :username : password : Here is your token : 0f0db6ff7eb32259e2ab26faad5bea05c5f31a2e59a38743077acd22e4512d6907687b0f26a0485b8e70f508c3c2902b6e1373a93bd585792817170366e57f43
welcome guest
Now to obtain the admin access we need to replace the last block of the first token (containing 0} + padding
) by the second block of the second token ( containing 1
) and concat the last block of the last token ( containing } + padding
)
At this moment the json value will be :
Copy {"username": "AA", "password": "AAAAAA", "uid": 1}
Copy $ python3
>>> token1 = "0f0db6ff7eb32259e2ab26faad5bea05c5f31a2e59a38743077acd22e4512d69eb6c532e83118ae9a89f176933e2cd0f7e8c582ca3535bc548f256f69832be4f"
>>> token2 = "0f0db6ff7eb32259e2ab26faad5bea050ebd6b5fd3363bc885c982fd44c1e7f3c5f31a2e59a38743077acd22e4512d69eb6c532e83118ae9a89f176933e2cd0f7e8c582ca3535bc548f256f69832be4f"
>>> token3 = "0f0db6ff7eb32259e2ab26faad5bea05c5f31a2e59a38743077acd22e4512d6907687b0f26a0485b8e70f508c3c2902b6e1373a93bd585792817170366e57f43"
>>> forged = token1 [: - 32 ] + token2 [ 32 : 64 ] + token3 [ - 32 :]
>>> forged
'0f0db6ff7eb32259e2ab26faad5bea05c5f31a2e59a38743077acd22e4512d69eb6c532e83118ae9a89f176933e2cd0f0ebd6b5fd3363bc885c982fd44c1e7f36e1373a93bd585792817170366e57f43'
And when we use it :
Copy $ python3 chall.py
Welcome !
Please login first.
How would you login ?
1 ) Token
2 ) Credentials
Choice :1
Token : 0f0db6ff7eb32259e2ab26faad5bea05c5f31a2e59a38743077acd22e4512d69eb6c532e83118ae9a89f176933e2cd0f0ebd6b5fd3363bc885c982fd44c1e7f36e1373a93bd585792817170366e57f43
welcome admin
Flag = FLAG{ECBForgingBlock}
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 = getToken ( "A" * 16 * 2 , "" )
# Getting padding size to end the first block and then write an arbitrary block
while token [ 32 : 64 ] != token [ 64 : 96 ]:
padding += 1
token = getToken ( "A" * padding + "A" * 16 * 2 , "" )
print ( f "padding is size { padding } " )
token1 = getToken ( "A" * padding, "A" * 6 )
token2 = getToken ( "A" * padding + " " * 15 + "1" , "" )
token3 = getToken ( "A" * padding, "A" * 5 )
forged = token1 [: - 32 ] + token2 [ 32 : 64 ] + token3 [ - 32 :]
authToken (forged)
Exercice
If you want to try this exploit by yourself, you can pull this docker image :
Copy docker pull thectfrecipes/crypto:ECBForgingBlock
Deploy the image using the followed command :
Copy docker run --name thectfrecipes_crypto_ECBForgingBlock -it --rm -d -p 1337:1337 thectfrecipes/crypto:ECBForgingBlock
The service is available on port 1337