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