Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
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