edit

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

{"registration_t

oken": "AAAAAAAA

AAAAAAAAAAAAAAAA

AAAAAAAA", "user

name": "davey_jo

nes", "bio": "On

ce in power, I s

hall bring Valve

rdian\'s fleet t

o its former glo

ry!"}

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}


HomeAboutContact