Bleichenbacher Attack in Django with Dynamodb
Bleichenbacher Attack in Django with Dynamodb — how this specific combination creates or exposes the vulnerability
A Bleichenbacher attack exploits adaptive chosen-ciphertext in RSA-based padding schemes (e.g., PKCS#1 v1.5). In a Django application using Dynamodb as the session or user store, this can manifest when asymmetric encryption or signed tokens (such as JWTs or encrypted session cookies) are validated server-side. If Django decrypts or verifies ciphertexts and returns distinguishable responses—such as different HTTP status codes, timing differences, or error messages—an attacker can iteratively decrypt or forge tokens without knowing the private key.
When Dynamodb is used as the persistence layer, the application might store encrypted or signed tokens as item attributes. If the validation logic in Django leaks padding errors or timing information, the attacker can send many modified ciphertexts and observe subtle differences in behavior. Because Dynamodb access patterns and response times are generally consistent, timing side channels become more observable, aiding the attacker’s adaptive oracle. Moreover, if session identifiers or API tokens stored in Dynamodb are derived from or protected by RSA encryption, a successful Bleichenbacher attack can lead to session hijacking or privilege escalation (e.g., assuming an admin identity by forging a token).
Specific risk scenarios include:
- JWT tokens encrypted with RSA-OAEP or PKCS#1 where Django validates them using a public key and stores metadata in Dynamodb; padding errors or timing differences enable iterative decryption.
- Session cookies that reference Dynamodb-stored encrypted session blobs; if validation is not constant-time, an attacker can recover plaintext or forge valid session records.
- OAuth or SSO flows where an RSA-signed ID token is verified against attributes in Dynamodb; a Bleichenbacher oracle can bypass signature integrity by exploiting error behavior.
To detect this class of issue with middleBrick, you can submit your endpoint for an unauthenticated scan. The 12 security checks include Input Validation and LLM/AI Security, which can surface padding-related anomalies and token-handling weaknesses. Findings include severity, remediation guidance, and mappings to frameworks such as OWASP API Top 10 and PCI-DSS.
Dynamodb-Specific Remediation in Django — concrete code fixes
Remediation focuses on removing adaptive behavior and ensuring cryptographic operations do not leak timing or padding information. Use constant-time comparison for tokens, prefer authenticated encryption with strong symmetric algorithms (e.g., AES-GCM), and avoid RSA decryption in request paths when possible. If you must use RSA, use OAEP with constant-time padding checks and ensure errors are generic.
Below are concrete examples for handling encrypted or signed data with Dynamodb in Django, emphasizing safe patterns.
Example 1: Using AES-GCM for encryption with Dynamodb
Store encrypted data using AES-GCM, which provides confidentiality and integrity in a single pass, avoiding RSA padding issues entirely.
import base64
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def encrypt_data(plaintext: str, key_b64: str) -> dict:
key = base64.urlsafe_b64decode(key_b64)
aesgcm = AESGCM(key)
nonce = os.urandom(12)
ct = aesgcm.encrypt(nonce, plaintext.encode('utf-8'), associated_data=None)
return {
'ciphertext_b64': base64.urlsafe_b64encode(ct).decode('utf-8'),
'nonce_b64': base64.urlsafe_b64encode(nonce).decode('utf-8')
}
def store_user_data(user_id: str, data: dict, table):
encrypted = encrypt_data(str(data), os.getenv('AES_GCM_KEY_64'))
table.put_item(Item={
'user_id': user_id,
'encrypted_data': encrypted['ciphertext_b64'],
'nonce': encrypted['nonce_b64']
})
def get_user_data(user_id: str, table):
resp = table.get_item(Key={'user_id': user_id})
item = resp.get('Item')
if not item:
return None
key = base64.urlsafe_b64decode(os.getenv('AES_GCM_KEY_64'))
aesgcm = AESGCM(key)
nonce = base64.urlsafe_b64decode(item['nonce'])
pt = aesgcm.decrypt(nonce, base64.urlsafe_b64decode(item['encrypted_data']), associated_data=None)
return eval(pt.decode('utf-8')) # prefer structured parsing in production
# Example usage with boto3 resource
table = boto3.resource('dynamodb', region_name='us-east-1').Table('UserSecrets')
store_user_data('usr-123', {'ssn': '123-45-6789'}, table)
print(get_user_data('usr-123', table))
Example 2: Constant-time token verification when RSA is required
If RSA is unavoidable (e.g., verifying third-party JWTs), ensure verification uses constant-time comparisons and generic error handling.
import jwt
import time
from django.conf import settings
def verify_jwt_constant_time(token: str, public_key) -> dict:
# Use options to avoid timing leaks in verification when possible
try:
# Enforce algorithms explicitly and avoid accepting 'none'
decoded = jwt.decode(
token,
public_key,
algorithms=['RS256'],
options={'verify_signature': True, 'require': ['exp', 'iss']}
)
return decoded
except jwt.PyJWTError:
# Return a generic error to avoid distinguishing padding or signature faults
raise jwt.InvalidTokenError('Invalid token')
# Example of usage within a Django view
from django.http import JsonResponse, HttpResponseBadRequest
def protected_view(request):
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
return HttpResponseBadRequest('Unauthorized')
token = auth.split(' ')[1]
try:
payload = verify_jwt_constant_time(token, settings.PUBLIC_RSA_KEY)
# Continue with business logic; do not leak token validity details
return JsonResponse({'ok': True, 'sub': payload.get('sub')})
except jwt.InvalidTokenError:
return HttpResponseBadRequest('Unauthorized')
# Ensure Dynamodb reads are consistent and do not expose timing differences
import boto3
from django.core.cache import cache
def get_session_cached(session_id: str, table):
cached = cache.get(f'session_{session_id}')
if cached is not None:
return cached
resp = table.get_item(Key={'session_id': session_id})
item = resp.get('Item')
# Constant-time dummy read to obscure timing when missing (optional)
if not item:
cache.set(f'session_{session_id}', {}, timeout=60)
return {}
cache.set(f'session_{session_id}', item, timeout=300)
return item
Operational and architectural mitigations
- Never use RSA decryption in hot request paths; offload to dedicated services if necessary.
- Prefer authenticated encryption (AES-GCM, ChaCha20-Poly1305) for data at rest and in transit.
- Ensure error messages are uniform and do not distinguish between missing items, bad padding, or invalid signatures.
- Use middleware to enforce constant-time checks where cryptographic verification occurs.
- Rotate keys regularly and audit third-party token validation logic.
middleBrick can scan your endpoints to highlight cryptographic misuse and token-handling risks. The scans include input validation and LLM/AI Security checks, which can surface weak cryptography and oracle behaviors. Results provide severity-ranked findings and remediation guidance mapped to standards such as OWASP API Top 10 and PCI-DSS.