CBC padding oracle
TL;DR: Server distinguishes valid from invalid PKCS#7 padding; that one bit decrypts entire ciphertexts.
What it is
A padding oracle is any service that, while decrypting CBC ciphertext, behaves differently when PKCS#7 padding is malformed versus when it is well-formed. The leak can be an HTTP 500 vs 200, a different error string, or even a timing delta. With that single bit per query, an attacker can decrypt arbitrary CBC ciphertext block-by-block and — by running the algorithm against a chosen plaintext — also encrypt arbitrary data without knowing the key. Originally published by Vaudenay (2002); famous productisations include POODLE and the ASP.NET viewstate oracle.
Preconditions / where it applies
- CBC mode (see block-cipher-modes) with PKCS#7 padding
- Attacker can submit chosen ciphertexts and observe a side channel revealing “padding valid” vs “padding invalid”
- Common targets: session cookies, viewstate, encrypted URL parameters, JWE with
A128CBC-HS256when the MAC is checked after padding
Technique
Recall CBC decryption: P_i = D_k(C_i) XOR C_{i-1}. Let I_i = D_k(C_i) (the intermediate). If we control C_{i-1}, we control P_i = I_i XOR C_{i-1} and we know C_{i-1}.
To decrypt block C_i, submit ciphertext C' || C_i where C' is a 16-byte block we mutate. The server decrypts as P' = I_i XOR C'.
- Set the last byte of
C'to a guessgfrom 0..255. The plaintext last byte becomesI_i[15] XOR g. The padding check passes when that byte equals0x01(or by chance the trailing run is valid). - The valid guess
ggivesI_i[15] = g XOR 0x01. - Move to byte 14: set
C'[15] = I_i[15] XOR 0x02so the last plaintext byte is0x02, then brute-forceC'[14]until padding0x02 0x02is accepted. That givesI_i[14]. - Repeat to recover all 16 bytes of
I_i, thenP_i = I_i XOR C_{i-1}(the real previous block). - Chain across all blocks of the message; ~128 × n_blocks requests per message in the worst case, ~16 on average per byte.
Encryption oracle: pick a target plaintext, recover I_i of any block, then choose C_{i-1} = I_i XOR P_target to force decryption to a chosen plaintext. Iterate from the last block backwards to forge arbitrary ciphertext.
Practical tooling:
1
padbuster https://target/app/session=BASE64CT BASE64CT 16 -encoding 0 -cookies "..."
hashcat-style mass decoding is also available via python-paddingoracle and Burp’s “Padding Oracle” extension. For length-extension-style block stitching see also cbc-bit-flipping.
Detection and defence
- Server logs show a flood of decrypt errors on the same endpoint with mutated ciphertexts; rate-limit and alert
- Use authenticated encryption (AES-GCM, ChaCha20-Poly1305) so any ciphertext mutation fails MAC before padding is even looked at
- If you must use CBC, encrypt-then-MAC and verify the MAC in constant time before decrypting; return a single generic error
- WAF rules can match base64 ciphertexts with high entropy of mutation on a single parameter
References
- Vaudenay — Security flaws induced by CBC padding — original paper
- PortSwigger — Cracking CBC encryption (padding oracle) — practical lab walkthrough
- HackTricks — Padding oracle — exploitation notes
- GitHub — AonCyberLabs/PadBuster — canonical exploitation tool