Padding oracle: decrypting without ever knowing the key
When the server distinguishes "invalid padding" from "valid padding", it leaks enough information to reconstruct the plaintext, block after block.
The principle fits in one sentence: in CBC mode, each plaintext block depends on the previous ciphertext block via a XOR. If the server tells us—even indirectly—whether the decrypted padding is correct, we can manipulate that previous block to guess each plaintext byte.
The oracle
An oracle is any observable behavior that separates valid padding from invalid: a different HTTP status, an error message, or simply the response time. Here, a 500 error on broken padding and a 200 otherwise. Enough.
C[i-1] (which we'll forge) and C[i] (which we want to decrypt). By playing with C[i-1], we force the last decrypted byte to equal 0x01 (a valid one-byte padding). At that point, a XOR gives us the intermediate byte, hence the plaintext.
Byte-by-byte attack
We brute-force the last byte of the forged block (256 tries max) until we get valid padding, then work our way up to 0x02 0x02, 0x03 0x03 0x03, and so on. Skeleton:
def decrypt_block(c_prev, c_target): inter = bytearray(16) for pad in range(1, 17): forged = bytearray(16) for k in range(1, pad): forged[-k] = inter[-k] ^ pad for guess in range(256): forged[-pad] = guess if oracle(bytes(forged) + c_target): inter[-pad] = guess ^ pad break return bytes(a ^ b for a, b in zip(inter, c_prev))
We repeat over every block, sliding the window. The IV plays the role of "previous block" for the very first block.
0x02 0x02 by chance when we were aiming for 0x01. We resolve the ambiguity by modifying the second-to-last byte and rechecking. Without that guard, the attack derails on certain blocks.
flag{cbc_p4dd1ng_l34ks_0n3_byt3_4t_4_t1m3}
Fix it
- Use an authenticated mode (AES-GCM, or encrypt-then-MAC). A MAC verified before decryption silences the oracle.
- Never leak the cause of a decryption failure—even a timing difference is enough.
- Reject early, uniformly, without distinguishing padding vs integrity vs format.
Encryption without authentication is a lock with no strike plate: it looks shut, but it pushes open.