Double Free in Django with Hmac Signatures
Double Free in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
In Django, a Double Free risk can arise when HMAC-based signing is used to protect data integrity but the application logic fails to validate signature provenance before processing the payload. HMAC Signatures in Django are commonly implemented via django.core.signing or custom HMAC schemes that sign serialized data (e.g., JSON or pickled structures) and append the signature as a query parameter or header. A Double Free occurs when the same signed token is consumed more than once in a way that causes side effects such as double writes, double charges, or repeated state changes.
Consider a payment flow where a signed token encodes a transaction amount and a user identifier. If the token is verified with HMAC and then processed multiple times due to retries, race conditions, or lack of idempotency checks, the backend may apply the same transaction twice. This is not a cryptographic flaw in HMAC itself (the signature remains valid), but a design flaw where the system does not track token usage. The vulnerability is exposed when the unsigned data derived from the token is re-applied or re-executed, leading to unauthorized operations.
Attackers can exploit this by intercepting a valid signed request and replaying it, or by crafting scenarios where the server re-processes the same signed payload. For example, an unsigned POST body may be combined with a valid HMAC header to trigger duplicate resource creation. Since HMAC only ensures integrity and authenticity, Django developers must add uniqueness constraints, idempotency keys, or transaction deduplication to prevent Double Free conditions. Without these controls, the combination of Hmac Signatures and state-changing operations creates a window for resource exhaustion or inconsistent state.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
Remediation focuses on ensuring each signed payload is consumed at most once and that state changes are idempotent. Use signed tokens with embedded uniqueness (e.g., a UUID or nonce) and enforce one-time validation on the server.
Example 1: Django signing with a nonce and idempotency key
import uuid
import json
from django.core import signing
from django.http import JsonResponse
from django.views.decorators.http import require_POST
SECRET_KEY = 'your-secret-key' # In settings, use settings.SECRET_KEY
def create_signed_token(data):
payload = {
'data': data,
'nonce': str(uuid.uuid4()), # unique per request
'iat': int(time.time()),
}
signed = signing.dumps(payload, key=SECRET_KEY)
return signed
@require_POST
def process_payment(request):
signed_token = request.POST.get('token')
if not signed_token:
return JsonResponse({'error': 'missing token'}, status=400)
try:
payload = signing.loads(signed_token, key=SECRET_KEY, max_age=300)
except signing.BadSignature:
return JsonResponse({'error': 'invalid signature'}, status=400)
# Idempotency check: store processed nonce in cache/database
from django.core.cache import cache
if cache.get(f"processed_nonce:{payload['nonce']}"):
return JsonResponse({'error': 'duplicate request'}, status=409)
# Process payment safely
amount = payload['data'].get('amount')
user_id = payload['data'].get('user_id')
# ... perform payment logic ...
# Mark nonce as processed
cache.set(f"processed_nonce:{payload['nonce']}", True, timeout=86400)
return JsonResponse({'status': 'ok'})
Example 2: HMAC verification with replay protection using a database model
import hmac
import hashlib
import time
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from .models import ProcessedRequest
def verify_hmac_signature(data, signature, secret):
computed = hmac.new(
secret.encode(),
msg=data.encode(),
digestmod=hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
@require_POST
def handle_webhook(request):
body = request.body.decode('utf-8')
signature = request.META.get('HTTP_X_SIGNATURE')
secret = 'webhook-secret'
if not verify_hmac_signature(body, signature, secret):
return JsonResponse({'error': 'invalid signature'}, status=400)
# Prevent replay: store request hash with timestamp
import hashlib
request_hash = hashlib.sha256(body.encode()).hexdigest()
if ProcessedRequest.objects.filter(request_hash=request_hash).exists():
return JsonResponse({'error': 'duplicate webhook'}, status=409)
# Process webhook data
# ...
ProcessedRequest.objects.create(request_hash=request_hash, received_at=int(time.time()))
return JsonResponse({'status': 'processed'})
Best practices summary
- Include a unique nonce or idempotency key within the signed payload and enforce one-time use.
- Use
hmac.compare_digestto avoid timing attacks during signature verification. - Store processed nonces or request hashes with an appropriate TTL to bound storage growth.
- Combine HMAC validation with rate limiting and transaction-level deduplication for defense in depth.