In this challenge, we see that basically each time we connect to the server and input our name we are given a token, which enables us to login with this username. By looking at the code we see that toto
seems to be an admin account. Thus we will try to generate his token.
We see that the token is generated by encrypting the usename and his privileges and their CRC32 checksum with AES in OFB mode. Furthermore the iv
is provided to us. So as we can see on this wikipedia image, if we possess one valid token then we only need to XOR it to the new username and checksum to get a new valid token.
For simplicity we took bob as a username so as the json dump of his status had exaclty the same length as the one of toto (True
has one letter less than False
)
So this is what we did
- Connect to the server and get the token for bob
- XOR it with
format(bob)+CRC32(bob)
- XOR it with
format(toto)+CRC32(toto)
- Send the new token to the server
- Get the flag
import json
import socket
from zlib import crc32
def connect_to_server(host: str, port: int) -> socket.socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
return s
def receive_until(s: socket.socket, text: bytes) -> str:
buffer = b''
while text not in buffer:
data = s.recv(1024)
if not data:
break
buffer += data
return buffer.decode('utf-8')
def forge_admin_token(normal_token_hex: str) -> str:
normal_token = bytes.fromhex(normal_token_hex)
normal_plaintext = json.dumps({'name': 'bob', 'admin': False}).encode()
normal_crc = crc32(normal_plaintext).to_bytes(4, byteorder='big')
target_plaintext = json.dumps({'name': 'toto', 'admin': True}).encode()
target_crc = crc32(target_plaintext).to_bytes(4, byteorder='big')
if len(normal_plaintext) != len(target_plaintext):
print("Lengths don't match, adjust your payload!")
exit(1)
keystream = bytes(c ^ p for c, p in zip(normal_token, normal_plaintext + normal_crc, strict=False))
forged_token = bytes(p ^ k for p, k in zip(target_plaintext + target_crc, keystream, strict=False))
return forged_token.hex()
def main() -> None:
HOST = 'chall.fcsc.fr'
PORT = 2150
print(f'[+] Connecting to {HOST}:{PORT}')
s = connect_to_server(HOST, PORT)
receive_until(s, b'>>> ')
print('[+] Selecting login option')
s.send(b'1\n')
receive_until(s, b'(y/n) ')
print("[+] Registering as 'bob'")
s.send(b'y\n')
receive_until(s, b'Name: ')
s.send(b'bob\n')
response = receive_until(s, b'>>> ')
token_lines = response.split('\n')
for i, line in enumerate(token_lines):
if 'Here is your token:' in line and i + 1 < len(token_lines):
normal_token_hex = token_lines[i + 1].strip()
print(f'[+] Received token: {normal_token_hex}')
break
forged_token = forge_admin_token(normal_token_hex)
print(f"[+] Forged token for 'toto' with admin=true: {forged_token}")
s.send(b'1\n')
receive_until(s, b'(y/n) ')
s.send(b'n\n')
receive_until(s, b'Token: ')
s.send(f'{forged_token}\n'.encode())
result = receive_until(s, b'>>> ')
print('\n=== RESULT ===\n' + result)
s.close()
if __name__ == '__main__':
main()