Padding Oracle in Django with Bearer Tokens
Padding Oracle in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
A padding oracle attack exploits how a cryptographic implementation reveals whether decrypted data is valid before returning a definitive error. In Django, this often relates to custom or improperly used token-based schemes where encrypted or MAC-protected payloads are processed. When Bearer Tokens are handled with a cipher or MAC that provides a padding oracle — for example, using AES in a mode where padding validation errors are distinguishable — an attacker can iteratively submit modified ciphertexts and observe differences in responses to infer plaintext without needing the key.
Consider a Django service that protects API access with Bearer Tokens stored as encrypted values. If the server decrypts the token, validates padding, and then checks structure (e.g., user ID or scopes) before returning a clear error, the timing or response behavior can leak information about padding correctness. Even when the token is transmitted via the Authorization header as a Bearer credential, the server-side decryption and validation path remains a target. A common vulnerable pattern is using low-level cryptographic operations without constant-time padding verification, allowing an attacker who can make authenticated-context requests to gradually recover the plaintext token.
In practice, this means an attacker who can observe HTTP status codes or timing differences — for example, 401 versus 400, or subtle latency changes — can treat the API as an oracle. They modify bytes in the Bearer Token ciphertext and use the server’s responses to guide a byte-by-byte recovery. Because the token is transmitted in a standard Authorization: Bearer
An important note: middleBrick detects scenarios where API responses vary based on padding validity and surfaces these as findings under Input Validation and Data Exposure checks, helping you identify oracle-like behavior without needing to understand the internal cryptography. This is especially relevant when OpenAPI/Swagger specs describe security schemes using bearer formats but the implementation does not enforce constant-time validation.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
Remediation centers on ensuring that any decryption or MAC verification used for Bearer Tokens runs in constant time and does not expose distinguishable errors related to padding or signature validity. Below are concrete, realistic code examples for Django that illustrate secure handling.
1. Use high-level cryptographic APIs that avoid manual padding
Instead of implementing AES-CBC or similar low-level modes directly, use Django-friendly libraries that handle padding and verification safely. For example, using Fernet (from cryptography) ensures that padding and authentication are handled uniformly:
from cryptography.fernet import Fernet, InvalidToken
import os
# Generate or load a key securely (e.g., from settings.SECRET_KEY or an env var)
key = Fernet.generate_key()
f = Fernet(key)
def create_token(data: str) -> str:
return f.encrypt(data.encode('utf-8')).decode('utf-8')
def verify_token(token: str) -> str | None:
try:
return f.decrypt(token.encode('utf-8')).decode('utf-8')
except InvalidToken:
# Constant-time failure path; no details leaked about padding
return None
# In a view expecting Authorization: Bearer
def my_view(request):
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return HttpResponseForbidden()
token = auth[7:].strip()
payload = verify_token(token)
if payload is None:
# Always return the same generic error and status to avoid leaking
return HttpResponseForbidden()
# proceed safely
return HttpResponse('OK')
2. Constant-time comparison when validating MACs or signatures
If you must work with raw ciphertext and MACs, ensure comparison is constant-time:
import hmac
import hashlib
from django.http import HttpResponseForbidden
from django.conf import settings
def verify_hmac_token(token: str, received_mac: str) -> bool:
computed = hmac.new(settings.SECRET_KEY.encode(), token.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(computed, received_mac)
def my_view_hmac(request):
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return HttpResponseForbidden()
parts = auth[7:].split('.')
if len(parts) != 2:
return HttpResponseForbidden()
token, mac = parts
if not verify_hmac_token(token, mac):
return HttpResponseForbidden() # constant-time failure
return HttpResponse('OK')
3. Avoid returning early on padding-specific errors
Ensure that any decryption routine catches all padding-related exceptions and treats them identically to other failures. For example, when using PyCryptodome directly (not recommended for new code), wrap low-level calls so that validation errors do not distinguish padding from other issues:
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from django.http import HttpResponseForbidden
import secrets
def decrypt_aes_cbc_padded(ciphertext_b64: str, key_b64: str, iv_b64: str) -> str | None:
try:
key = secrets.token_bytes(32) # placeholder: load securely
iv = secrets.token_bytes(16) # placeholder: load securely
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(base64.b64decode(ciphertext_b64))
unpad(plaintext, AES.block_size) # may raise ValueError on bad padding
return plaintext.decode('utf-8')
except (ValueError, UnicodeDecodeError, base64.binascii.Error):
# Do not differentiate causes; return generic failure
return None
def my_view_aes(request):
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return HttpResponseForbidden()
token = auth[7:].strip()
payload = decrypt_aes_cbc_padded(token, settings.SECRET_KEY.encode(), b'1234567890123456')
if payload is None:
return HttpResponseForbidden()
return HttpResponse('OK')
These patterns emphasize treating token validation as an atomic operation with uniform responses, regardless of whether the failure originates from padding, MAC mismatch, or malformed input. This approach mitigates padding oracle risks even when Bearer Tokens are used.