Skip to main content

Free 30-min security demo Book Now

Academy Padding Oracle Attacks
Advanced · 25 min

Padding Oracle Attacks

Understand how CBC padding errors leak decryption information and why authenticated encryption prevents it.

1 How Padding Oracle Attacks Work

AES-CBC encryption requires plaintext to be padded to block boundaries (PKCS#7). When the server decrypts ciphertext and reveals padding errors differently from other errors, an attacker can exploit this "oracle" to decrypt any ciphertext without knowing the key.

The attack mechanism:

  1. Attacker modifies ciphertext bytes and submits to server
  2. Server decrypts and checks padding — returns "padding error" or "wrong data"
  3. Different error = information about decrypted bytes
  4. By systematically modifying ciphertext and observing errors, attacker recovers plaintext byte by byte

Real-world examples:

  • POODLE (2014): Exploited SSLv3 CBC padding
  • Lucky Thirteen: Timing-based padding oracle against TLS
  • ASP.NET viewstate padding oracle (MS10-070)

Vulnerable pattern:

try:
    plaintext = aes_cbc_decrypt(ciphertext, key, iv)
except PaddingError:
    return "Invalid padding"  # Oracle!
except Exception:
    return "Invalid data"  # Different message = different error = oracle!

2 Use Authenticated Encryption

The fundamental fix is to use authenticated encryption (AES-GCM or ChaCha20-Poly1305) instead of plain CBC. Authentication ensures the ciphertext has not been tampered with before decryption even begins — padding is never checked on malicious ciphertext.

AES-GCM eliminates padding oracles:

from Crypto.Cipher import AES
import os

def encrypt(key, plaintext):
    nonce = os.urandom(12)
    cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
    ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode())
    return nonce + tag + ciphertext

def decrypt(key, data):
    nonce, tag, ciphertext = data[:12], data[12:28], data[28:]
    cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
    try:
        # Authentication checked BEFORE decryption
        return cipher.decrypt_and_verify(ciphertext, tag).decode()
    except ValueError:
        # Generic error — no oracle information
        raise ValueError("Decryption failed")

Defense checklist:

  • Use AES-GCM or ChaCha20-Poly1305 instead of AES-CBC
  • Never return different error messages for padding vs other decryption errors
  • Verify the auth tag before decrypting when using CBC (Encrypt-then-MAC)
  • Never implement your own padding validation

Knowledge Check

0/3 correct
Q1

What information does a padding oracle leak to an attacker?

Q2

Why does AES-GCM prevent padding oracle attacks?

Q3

If you must use AES-CBC, what pattern prevents padding oracle attacks?

Code Exercise

Switch from CBC to GCM

The decryption function uses AES-CBC and returns different errors for padding vs other failures, creating a padding oracle. Rewrite it to use AES-GCM with authenticated decryption.

python

Put it into practice

Find Padding Oracle Attacks in your own code

Offensive360 SAST traces the tainted data flow behind this class of bug across 60+ languages and ships a secure fix with every finding. Public open-source projects scan free; teams can see it on their own repository in a 30-minute demo.