cryptofreemedium

Shambles

hackthebox

Task: Banking service encrypts JWT tokens and user data with AES-CBC using fixed KEY/IV; login endpoint leaks padding validity via distinct error messages. Solution: CBC padding oracle attack with batched queries to decrypt user data, recover IV from known JWT header, extract card number, and withdraw exact balance to get flag.

$ ls tags/ techniques/
cbc_padding_oracleiv_recovery_known_plaintextoracle_batching_pipeliningpkcs7_side_channel

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

Shambles — HackTheBox

Description

A banking-like service with login, retrieve user data, draw money options. Target credentials provided: D-Cryp7 / Cryp70Gr47Hy!.

A server provides 4 options: Login (option 1), Retrieve encrypted user data (option 2), Draw money (option 3), and Exit (option 4). The server uses AES-CBC encryption with a fixed KEY and IV (generated once at module level with os.urandom()) for encrypting JWT tokens and user data. The goal is to drain the target account balance to exactly zero to receive the flag.

Analysis

Source code review

server.py — The critical vulnerability is in the login handler (option 1). When a token is provided:

cipher = AES.new(KEY, AES.MODE_CBC, IV) try: token = unpad(cipher.decrypt(bytes.fromhex(creds["token"])), 16) except: send("Decryption error!") # ← padding failure try: payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) ... except: send("Validation error!") # ← JWT validation failure

Two distinct error messages create a padding oracle: "Decryption error!" means PKCS7 padding was invalid, while "Validation error!" means padding was valid but the JWT was malformed. This distinction lets us determine whether any crafted ciphertext decrypts to valid PKCS7 padding.

Key observations:

  1. Fixed KEY and IVKEY = os.urandom(16) and IV = os.urandom(16) are generated once at module level and reused for ALL encrypt/decrypt operations (login tokens AND user data)
  2. Option 2 returns AES-CBC(pad(card_number + str(balance))) — encrypted with the same KEY and IV
  3. Option 3 requires the correct card_number; if balance drops to exactly 0, the flag is revealed
  4. db.py has assert money_left >= 0 preventing overdraft — we must withdraw the exact integer amount

Attack plan

...