$ cat writeup.md…
$ cat writeup.md…
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.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
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.
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:
KEY = 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)AES-CBC(pad(card_number + str(balance))) — encrypted with the same KEY and IVassert money_left >= 0 preventing overdraft — we must withdraw the exact integer amount...
$ grep --similar