CyberSci Regionals 2024: candidate_registry
Only verified candidates can access this portal.. maybe we can be one?
We’re given the source code of a server:
server.py
import aes
import json
import string
from secret import FLAG, KEY, IV, REGISTRATION_TOKEN
KEY_SIZE = 16
TOKEN_SIZE = 32
assert len(KEY) == KEY_SIZE
assert len(IV) == KEY_SIZE
assert len(REGISTRATION_TOKEN) == TOKEN_SIZE
assert all(c in string.hexdigits for c in REGISTRATION_TOKEN)
def encrypt_input(input_payload, iv):
input_payload = json.dumps(input_payload)
input_payload_bytes = input_payload.encode()
cipher = aes.AES(KEY)
encrypted_input_bytes = cipher.encrypt_pcbc(input_payload_bytes, iv)
encrypted_input_hex = encrypted_input_bytes.hex()
return iv.hex() + encrypted_input_hex
def decrypt_input(encrypted_input_hex):
encrypted_input_bytes = bytes.fromhex(encrypted_input_hex)
iv = encrypted_input_bytes[:KEY_SIZE]
encrypted_input_bytes = encrypted_input_bytes[KEY_SIZE:]
cipher = aes.AES(KEY)
decrypted_input_bytes = cipher.decrypt_pcbc(encrypted_input_bytes, iv)
decrypted_input = decrypted_input_bytes.decode('ascii', errors='ignore')
input_payload = json.loads(decrypted_input)
return input_payload
def receive_input():
encrypted_input_hex = input('Enter the encrypted input hex: ')
try:
input_payload = decrypt_input(encrypted_input_hex)
except:
return None
return input_payload
REGISTERED_USERNAMES = []
def register_candidate(input_payload):
username = input_payload.get('username')
registration_token = input_payload.get('registration_token')
if not username:
return False, 'Username is required.'
if not registration_token or registration_token != REGISTRATION_TOKEN:
return False, 'Invalid registration token.'
if username in REGISTERED_USERNAMES:
return False, 'Username already registered.'
REGISTERED_USERNAMES.append(username)
return True, f'You are now registered as {username}.'
davey_jones_input = {
'registration_token': REGISTRATION_TOKEN,
'username': 'davey_jones',
'bio': "Once in power, I shall bring Valverdian's fleet to its former glory!"
}
davey_jones_encrypted_input = encrypt_input(davey_jones_input, IV)
registered, message = register_candidate(davey_jones_input)
assert registered
print('=============================================')
print("You have intercepted Davey Jones' submission:")
print(davey_jones_encrypted_input)
print('=============================================')
print("""
Welcome to the Candidate Registry.
To register your candidate account and receive access to the portal,
submit your username, bio, and the registration token you received from the registrar.
""")
input_payload = receive_input()
print()
if not input_payload:
print('Invalid input!')
exit()
registered, message = register_candidate(input_payload)
print(message)
print()
if registered:
print('Here is your password:', FLAG)
This server gives us a ciphertext for a json object with a username
field. We need to give it another object with the same REGISTRATION_TOKEN
field but a different username
.
Something’s weird here: the AES encryption and decryption functions have pcbc
in the name. Perhaps a modified version of CBC mode? Well, let’s look that up on our good friend Wikipedia.
[…]
On a message encrypted in PCBC mode, if two adjacent ciphertext blocks are exchanged, this does not affect the decryption of subsequent blocks.
[…]
Haha! So we need to exchange “two adjacent ciphertext blocks”. Let’s do a json.dumps
ourself to figure out the plaintext and cut it up into blocks…
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|
|
|
|
|
|
|
|
|
|
|
We want there to still be the username field, so we can’t change 4 or 5, and modify the username itself, so blocks 6 and 7 are what we’ll want to exchange.
Note that we need to use the “1-indexed” offset indices here because the IV is prepended to the ciphertext.
from pwn import *
r = remote("10.0.2.11", 10001)
B = 16*2 # the size of a block is 16 bytes -> 32 hex digits
r.recvuntil(b"Davey Jones' submission:\n")
msg = r.recvline().decode().strip()
before = msg[:B*6]
block_a = msg[B*6:B*7]
block_b = msg[B*7:B*8]
after = msg[B*8:]
r.sendline((before + block_b + block_a + after).encode())
print(r.recvall())
And we get the flag: cybersci{pcbc_1s_n0t_cbc_dsh8sdj9}
Home About Contact