Blocks

Download as pdf or txt
Download as pdf or txt
You are on page 1of 5

blocks

2023-10-04
300 pts
Description
Man I'm really bad at Tetris. Can you help me get into an admin's account so I hack my score and get to the
top of the scoreboard?
Files
src/app.py
Solution
This challenge presents a simple online leaderboard where users can play a game and submit their scores.
We are asked to gain control of an "admin" account.
By auditting the code we can see that it's a fairly standard Flask application however it uses custom
encrypted sessions using AES.
CIPHER_KEY = app.config["CIPHER_KEY"]
CIPHER = AES.new(CIPHER_KEY, AES.MODE_ECB)

class EncryptedSessionInterface(SessionInterface):
session_class = EncryptedSession

def open_session(self, app, request):


"""
Mostly copied from Flask but a little different

https://github.com/pallets/flask/blob/4240ace59710d86c478111affd4ad6fb4c8cad9e/src/fl
ask/sessions.py#L350
"""
sid = request.cookies.get(app.session_cookie_name)
if not sid:
return self.session_class()

# Decrypt session
sid = sid.encode()
Copyright © 2023 CTFd LLC
sid = binascii.unhexlify(sid)
sid = CIPHER.decrypt(sid).decode()
sid = sid.rstrip("#")
name, user_id = sid.split(":")
data = {
"username": name,
"id": user_id,
}
return self.session_class(data)

def save_session(self, app, session, response):


"""
Mosty copied from Flask but a little different

https://github.com/pallets/flask/blob/4240ace59710d86c478111affd4ad6fb4c8cad9e/src/fl
ask/sessions.py#L366
"""
name = self.get_cookie_name(app)
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)

# If the session is modified to be empty, remove the cookie.


# If the session is empty, return without setting the cookie.
if not session:
if session.modified:
response.delete_cookie(
name, domain=domain, path=path, secure=secure, samesite=samesite
)

return

# Add a "Vary: Cookie" header if the session was accessed at all.


if session.accessed:
response.vary.add("Cookie")

if not self.should_set_cookie(app, session):


return

httponly = self.get_cookie_httponly(app)
expires = self.get_expiration_time(app, session)

# Encrypt session
sid = f"{session['username']}:{session['id']}"
adjust = ((len(sid) // 16) + 1) * 16
sid = sid.ljust(adjust, "#")
val = CIPHER.encrypt(sid.encode())
val = binascii.hexlify(val)

response.set_cookie(
Copyright © 2023 CTFd LLC
name,
val, # type: ignore
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
)

The code in this section is interesting as it modifies the built in Flask session mechanism for custom
encrypted sessions containing the user's username and id. In addition, we can see that the sessions are
encrypted with AES using ECB mode.
ECB mode is known to be an insecure cipher mode of block cryptography for a few reasons:
Each block to be encrypted is independently encrypted from each other. This often leads to
identifiable patterns in the encrypted data.
Blocks are not connected to each other and are seperately encrypted. This means that an attacker can
create new ciphertexts by manipulating an existing ciphertext. Either by removing existing blocks
within the ciphertext or by shuffling the existing blocks.
For more details see this StackOverflow answer: https://crypto.stackexchange.com/a/20946
By taking advantage of these properties we can take the following steps:
1. First we need to determine the block size of the cipher. We can determine this by setting our
username to a repeating value in our profile. For example we can set our username to
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA. This value is 32 characters long which will help us determine if
the block size is 16, 24, or 32 bytes long.
2. We can then see our session is
b5b81e27438a08c2c6f76d34e6253ed2b5b81e27438a08c2c6f76d34e6253ed2e59896b2d73d4beef303
daaf451d9bb4 in the cookie. Notice that the first 32 characters are repeated
(b5b81e27438a08c2c6f76d34e6253ed2). By hex decoding this value we receive a 16 byte value which
implies that the block size is 16 bytes.
3. We then have to manipulate our session so that we can change the user ID by dropping the trailing
digits. Meaning we need to convert UID 1234 to UID 1 by pushing 234 to another block and then
cutting out that block.
4. To do this, we set our username to a 14 byte value. For example AAAAAAAAAAAAAA. Then your
encrypted session will look like AAAAAAAAAAAAAA:12345############. Notice that the 2345 are
beyond the first 16 bytes.
Copyright © 2023 CTFd LLC
5. Now we can remove the last 16 bytes of our session hex-decoded sesssion which should truncate off
2345############ leaving us with AAAAAAAAAAAAAA:1.

6. After hex encoding the session, we should be able to set the session as our session cookie and then
login as one of the admins.
Note that in this challenge, admins have the first 9 IDs so you can solve this challenge regardless of what
user ID your user has received.
NOTE: During review, it was noted that this challenge can be equally done with CBC mode as the user ID is
contained in the last block. While this is true, ECB mode simplifies the problem and also would allow for
additional challenge paths including the ability to shuffle the blocks around. For example if we needed to
use the username to craft a new encrypted block.
Exploit Code:
import binascii
import random
import string

def random_username():
return "".join(
random.choice(string.ascii_letters + string.digits) for _ in range(14)
)

print("Set your username to the following value")


new_name = random_username()
print(new_name)

print(
"This will push all digits of your user ID except for the first digit beyond the
16 byte block boundary"
)

print(
"For example if your user ID was 12345, the first 16 bytes of your session cookie
would be AAAAAAAAAAAAAA:1 and the next 16 bytes would be 2345############"
)

print(
"Because the challenge is using ECB mode, we can now truncate off the last block
to remove the extra padding and also the extra digits in your user ID to force you to
become a low ID user (more likely to be an admin)"
)

# What is the session cookie now? Copyright © 2023 CTFd LLC


sid = input("Whats your session cookie? ").strip()

sid = binascii.unhexlify(sid)
chances = len(sid) // 16

print("Try one of these session IDs (likely the shortest one) to become an admin
user")

for block in range(chances):


end = len(sid) - (16 * block)
truncated_sid = sid[0:end]
print(binascii.hexlify(truncated_sid).decode())

Flags
flag{truly_you_are_the_block_master}

truly_you_are_the_block_master

Copyright © 2023 CTFd LLC

You might also like