Challenge example

Arbitrary token

Code source example

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 ?

$ 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 :

+------------------+------------------+------------------+--------------+
| {'username': "AA | ", 'password': ' | AAAAAA', 'uid':  | 0} + padding |
+------------------+------------------+------------------+--------------+
$ 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

$ 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 :

# } + 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 :

{"username": "AA", "password": "AAAAAA", "uid":                1}
$ python3
>>> token1 = "0f0db6ff7eb32259e2ab26faad5bea05c5f31a2e59a38743077acd22e4512d69eb6c532e83118ae9a89f176933e2cd0f7e8c582ca3535bc548f256f69832be4f"
>>> token2 = "0f0db6ff7eb32259e2ab26faad5bea050ebd6b5fd3363bc885c982fd44c1e7f3c5f31a2e59a38743077acd22e4512d69eb6c532e83118ae9a89f176933e2cd0f7e8c582ca3535bc548f256f69832be4f"
>>> token3 = "0f0db6ff7eb32259e2ab26faad5bea05c5f31a2e59a38743077acd22e4512d6907687b0f26a0485b8e70f508c3c2902b6e1373a93bd585792817170366e57f43"
>>> forged = token1[:-32] + token2[32:64] + token3[-32:]
>>> forged
'0f0db6ff7eb32259e2ab26faad5bea05c5f31a2e59a38743077acd22e4512d69eb6c532e83118ae9a89f176933e2cd0f0ebd6b5fd3363bc885c982fd44c1e7f36e1373a93bd585792817170366e57f43'

And when we use it :

$ 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

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 :

docker pull thectfrecipes/crypto:ECBForgingBlock

Deploy the image using the followed command :

docker run --name thectfrecipes_crypto_ECBForgingBlock -it --rm -d -p 1337:1337 thectfrecipes/crypto:ECBForgingBlock

The service is available on port 1337

nc 127.0.0.1 1337

Last updated