The challenge gives us a menu where we can register, receive an access token, and later use that token to show the flag. The interesting part is the token generation:
KEY = token_hex(16)
def get_mac(data: bytes) -> str:
return sha1(KEY.encode("latin1") + data).hexdigest()
def generate_token(values: dict) -> str:
token = "|".join(f"{key}={value}" for key, value in values.items())
secure_token = f"{token}|mac={get_mac(token.encode('latin1'))}"
return b64encode(secure_token.encode("latin1")).decode("latin1")
The token contains user-controlled data and a MAC. The server checks the MAC before parsing the values:
token, mac = token.split(b"|mac=")
if get_mac(token) != mac.decode("latin1"):
return None
The bug is that this is
Here the key is generated with
from pwn import *
from base64 import b64decode, b64encode
import hashpumpy
remote = remote(
"4cqp7g67yflsc4hzda4yctsm44-1024-intro-crypto-2.challenge.cscg.live",
443,
ssl=True,
)
remote.recvuntil(b"Enter your choice: ")
remote.sendline(b"1")
remote.recvuntil(b"What is you name? ")
remote.sendline(b"test")
remote.recvuntil(b"What is your favorite animal? ")
remote.sendline(b"test")
remote.recvuntil(b"Here is your access token: ")
access_token = remote.recvline().strip().decode()
raw = b64decode(access_token)
data, mac = raw.split(b"|mac=")
new_mac, new_data = hashpumpy.hashpump(
mac.decode(),
data.decode(),
"|admin=true",
32,
)
forged = new_data + b"|mac=" + new_mac.encode()
forged_token = b64encode(forged).decode()
remote.recvuntil(b"Enter your choice: ")
remote.sendline(b"3")
remote.recvuntil(b"Enter access token: ")
remote.sendline(forged_token.encode())
print(remote.recvline().decode())
The parser stores values in a dictionary, so the later
CSCG{Should_h4ve_used_HMAC_or_KMAC_instead-.-}