Padding Oracle Anti-Pattern
Severity: High
Summary
Applications leak padding correctness during decryption through different error messages ("Invalid Padding" vs. "Decryption Failed") or timing differences. Attackers manipulate ciphertext and observe responses to decrypt entire messages byte-by-byte without knowing the key, breaking confidentiality.
The Anti-Pattern
The anti-pattern is using CBC mode and returning different responses based on decryption error type.
BAD Code Example
# VULNERABLE: The decryption function returns different error messages.
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from flask import request
KEY = b'sixteen byte key' # Should be randomly generated and managed securely
@app.route("/decrypt")
def decrypt_data():
encrypted_data = request.args.get('data').decode('hex')
iv = encrypted_data[:16]
ciphertext = encrypted_data[16:]
cipher = Cipher(algorithms.AES(KEY), modes.CBC(iv))
decryptor = cipher.decryptor()
try:
decrypted_padded = decryptor.update(ciphertext) + decryptor.finalize()
# Check padding
unpadder = padding.PKCS7(128).unpadder()
unpadded_data = unpadder.update(decrypted_padded) + unpadder.finalize()
return "Decryption successful!", 200
except ValueError as e:
# ORACLE: Different error responses leak information.
# Wrong padding raises ValueError with "Invalid padding".
# Other corruption causes different errors.
if "padding" in str(e).lower():
return "Error: Invalid padding.", 400
else:
return "Error: Decryption failed.", 500
# Attacker sends modified ciphertext, observes 400 vs. 500 errors,
# deduces plaintext information.
GOOD Code Example
# SECURE: Use an Authenticated Encryption with Associated Data (AEAD) mode like GCM.
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from flask import request
KEY = AESGCM.generate_key(bit_length=128) # Generate a secure key
def encrypt_gcm(data):
aesgcm = AESGCM(KEY)
nonce = os.urandom(12) # GCM uses a nonce
ciphertext = aesgcm.encrypt(nonce, data, None)
return nonce + ciphertext
@app.route("/decrypt/secure")
def decrypt_data_secure():
encrypted_data = request.args.get('data').decode('hex')
nonce = encrypted_data[:12]
ciphertext_with_tag = encrypted_data[12:]
aesgcm = AESGCM(KEY)
try:
# AEAD mode automatically verifies integrity (authentication tag).
# Tampering fails with single generic exception before padding step.
# (No padding in AEAD modes.)
decrypted_data = aesgcm.decrypt(nonce, ciphertext_with_tag, None)
return "Decryption successful!", 200
except InvalidTag:
# Any failure (tampering, corruption) → single generic error.
# No useful information for attacker.
return "Error: Decryption failed or data is corrupt.", 400
# If using CBC: Use "Encrypt-then-MAC" scheme.
# Compute MAC (HMAC-SHA256) of ciphertext, verify BEFORE decryption.
# Invalid MAC → reject without decryption.
Detection
- Review decryption code: Look for any code that decrypts data using CBC mode.
- Examine error handling: Check the
try...except blocks around decryption logic. Does the code catch different exceptions (e.g., PaddingError, CryptoError) and return different HTTP responses, status codes, or error messages for each?
- Look for timing differences: In some rare cases, the oracle can be a timing side channel, where valid padding checks take slightly longer than invalid ones. This is much harder to detect via code review.
- Perform active testing: Use a tool like
padbuster to actively test an endpoint for a padding oracle vulnerability.
Prevention
Related Security Patterns & Anti-Patterns
References
1---2name: padding-oracle-anti-pattern3description: Security anti-pattern for padding oracle vulnerabilities (CWE-649). Use when generating or reviewing code that decrypts CBC-mode ciphertext, handles decryption errors, or returns different errors for padding vs other failures. Detects error message oracles.4---56# Padding Oracle Anti-Pattern78**Severity:** High910## Summary1112Applications leak padding correctness during decryption through different error messages ("Invalid Padding" vs. "Decryption Failed") or timing differences. Attackers manipulate ciphertext and observe responses to decrypt entire messages byte-by-byte without knowing the key, breaking confidentiality.1314## The Anti-Pattern1516The anti-pattern is using CBC mode and returning different responses based on decryption error type.1718### BAD Code Example1920```python21# VULNERABLE: The decryption function returns different error messages.22from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes23from cryptography.hazmat.primitives import padding24from flask import request2526KEY = b'sixteen byte key' # Should be randomly generated and managed securely2728@app.route("/decrypt")29def decrypt_data():30 encrypted_data = request.args.get('data').decode('hex')31 iv = encrypted_data[:16]32 ciphertext = encrypted_data[16:]3334 cipher = Cipher(algorithms.AES(KEY), modes.CBC(iv))35 decryptor = cipher.decryptor()3637 try:38 decrypted_padded = decryptor.update(ciphertext) + decryptor.finalize()3940 # Check padding41 unpadder = padding.PKCS7(128).unpadder()42 unpadded_data = unpadder.update(decrypted_padded) + unpadder.finalize()4344 return "Decryption successful!", 2004546 except ValueError as e:47 # ORACLE: Different error responses leak information.48 # Wrong padding raises ValueError with "Invalid padding".49 # Other corruption causes different errors.50 if "padding" in str(e).lower():51 return "Error: Invalid padding.", 40052 else:53 return "Error: Decryption failed.", 5005455# Attacker sends modified ciphertext, observes 400 vs. 500 errors,56# deduces plaintext information.57```5859### GOOD Code Example6061```python62# SECURE: Use an Authenticated Encryption with Associated Data (AEAD) mode like GCM.63from cryptography.hazmat.primitives.ciphers.aead import AESGCM64from flask import request6566KEY = AESGCM.generate_key(bit_length=128) # Generate a secure key6768def encrypt_gcm(data):69 aesgcm = AESGCM(KEY)70 nonce = os.urandom(12) # GCM uses a nonce71 ciphertext = aesgcm.encrypt(nonce, data, None)72 return nonce + ciphertext7374@app.route("/decrypt/secure")75def decrypt_data_secure():76 encrypted_data = request.args.get('data').decode('hex')77 nonce = encrypted_data[:12]78 ciphertext_with_tag = encrypted_data[12:]7980 aesgcm = AESGCM(KEY)8182 try:83 # AEAD mode automatically verifies integrity (authentication tag).84 # Tampering fails with single generic exception before padding step.85 # (No padding in AEAD modes.)86 decrypted_data = aesgcm.decrypt(nonce, ciphertext_with_tag, None)87 return "Decryption successful!", 20088 except InvalidTag:89 # Any failure (tampering, corruption) → single generic error.90 # No useful information for attacker.91 return "Error: Decryption failed or data is corrupt.", 4009293# If using CBC: Use "Encrypt-then-MAC" scheme.94# Compute MAC (HMAC-SHA256) of ciphertext, verify BEFORE decryption.95# Invalid MAC → reject without decryption.96```9798## Detection99100- **Review decryption code:** Look for any code that decrypts data using CBC mode.101- **Examine error handling:** Check the `try...except` blocks around decryption logic. Does the code catch different exceptions (e.g., `PaddingError`, `CryptoError`) and return different HTTP responses, status codes, or error messages for each?102- **Look for timing differences:** In some rare cases, the oracle can be a timing side channel, where valid padding checks take slightly longer than invalid ones. This is much harder to detect via code review.103- **Perform active testing:** Use a tool like `padbuster` to actively test an endpoint for a padding oracle vulnerability.104105## Prevention106107- [ ] **Use AEAD cipher modes:** Best solution. AES-GCM or ChaCha20-Poly1305 combine encryption and authentication. Not vulnerable to padding oracles.108- [ ] **Use Encrypt-then-MAC with CBC:** Encrypt data, compute MAC (HMAC-SHA256) of ciphertext+IV. Verify MAC before decryption. Invalid MAC → reject immediately without decrypting.109- [ ] **Handle all errors identically:** Same generic error message and status code for bad padding, corrupt blocks, or invalid MACs.110111## Related Security Patterns & Anti-Patterns112113- [Weak Encryption Anti-Pattern](../weak-encryption/): Choosing a vulnerable mode like CBC without a MAC is a common weak encryption pattern.114- [Timing Attacks Anti-Pattern](../timing-attacks/): A related side-channel attack where information is leaked through how long an operation takes.115- [Verbose Error Messages Anti-Pattern](../verbose-error-messages/): A padding oracle is a specific type of verbose error message vulnerability.116117## References118119- [OWASP Top 10 A04:2025 - Cryptographic Failures](https://owasp.org/Top10/2025/A04_2025-Cryptographic_Failures/)120- [OWASP GenAI LLM10:2025 - Unbounded Consumption](https://genai.owasp.org/llmrisk/llm10-unbounded-consumption/)121- [CWE-649: Reliance on Obfuscation](https://cwe.mitre.org/data/definitions/649.html)122- [CAPEC-463: Padding Oracle Crypto Attack](https://capec.mitre.org/data/definitions/463.html)123- [Padding Oracle Attack (Wikipedia)](https://en.wikipedia.org/wiki/Padding_oracle_attack)124- [BlueKrypt - Cryptographic Key Length Recommendation](https://www.keylength.com/)125- Source: [sec-context](https://github.com/Arcanum-Sec/sec-context)