Missing Authentication in Django with Hmac Signatures
Missing Authentication in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
HMAC signatures are commonly used in Django to verify the integrity and origin of requests, for example when consuming webhook payloads or authorizing internal service calls. If authentication is missing or incorrectly implemented around HMAC verification, an attacker can forge requests that appear valid.
Consider a Django view that expects an X-Signature header containing an HMAC-SHA256 of the payload, using a shared secret stored in settings:
import hmac
import hashlib
def verify_hmac(request):
signature = request.META.get('HTTP_X_SIGNATURE')
if signature is None:
return False
computed = hmac.new(
key=settings.SHARED_SECRET.encode(),
msg=request.body,
digestmod=hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
Missing authentication becomes a risk when the verification step is bypassed, skipped, or never enforced. For example, if a developer forgets to call verify_hmac for certain routes, or routes are configured to allow both authenticated and unauthenticated access, the HMAC check is effectively disabled. Another common pattern is using a weak or predictable shared secret, or storing it in source control, which undermines the purpose of HMAC.
Even when HMAC is present, missing authentication can manifest if the signature is computed over incomplete data. If the timestamp or nonce used to prevent replay attacks is not included in the HMAC input, an attacker can reuse a valid signature. In Django, this often occurs when developers sign only the JSON body and ignore query parameters or headers that influence server-side behavior.
Additionally, if the view performs permission checks based on user roles but skips HMAC validation for some HTTP methods (e.g., allowing GET without a signature while requiring it for POST), the unauthenticated attack surface expands. An attacker could leverage the unsigned path to probe endpoints or exfiltrate data that should otherwise be protected by request integrity checks.
Insecure default configurations can also contribute. If Django’s ALLOWED_HOSTS is too permissive or if HTTPS is not enforced in production, intercepted signatures may be reused. MiddleBrick scans detect these patterns by correlating endpoint behavior with the presence (or absence) of HMAC checks across the unauthenticated attack surface, highlighting authentication gaps that could enable unauthorized operations.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
To secure Django endpoints using HMAC, ensure signature verification is mandatory, covers all relevant request data, and uses safe comparison. Below are concrete, working examples that address missing authentication and related weaknesses.
1. Enforce verification for all relevant methods and inputs
Always verify the HMAC for state-changing methods, and include query parameters and selected headers in the signed payload to prevent tampering:
import hmac
import hashlib
def verify_hmac_enforced(request):
signature = request.META.get('HTTP_X_SIGNATURE')
if signature is None:
return False
# Include query string to prevent parameter manipulation
query_string = request.META.get('QUERY_STRING', '')
payload = request.body
if query_string:
payload = payload + b'?' + query_string.encode()
computed = hmac.new(
key=settings.SHARED_SECRET.encode(),
msg=payload,
digestmod=hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
from django.http import JsonResponse, HttpResponseBadRequest
def webhook_view(request):
if request.method not in ('POST',):
return HttpResponseBadRequest('Method not allowed')
if not verify_hmac_enforced(request):
return HttpResponseBadRequest('Invalid signature')
# Process payload safely
return JsonResponse({'status': 'ok'})
2. Use a constant-time comparison and protect against replay
Include a timestamp and nonce in the signed data, and validate freshness on the server to mitigate replay attacks:
import time
def verify_hmac_with_replay_protection(request):
signature = request.META.get('HTTP_X_SIGNATURE')
timestamp = request.META.get('HTTP_X_TIMESTAMP')
nonce = request.META.get('HTTP_X_NONCE')
if signature is None or timestamp is None or nonce is None:
return False
# Reject old requests (e.g., 5 minutes)
if abs(time.time() - int(timestamp)) > 300:
return False
# Use a server-side cache or DB to track used nonces
if cache.get(f'nonce:{nonce}'):
return False
payload = request.body
computed = hmac.new(
key=settings.SHARED_SECRET.encode(),
msg=payload + b'|' + timestamp.encode() + b'|' + nonce.encode(),
digestmod=hashlib.sha256
).hexdigest()
if not hmac.compare_digest(computed, signature):
return False
cache.set(f'nonce:{nonce}', True, timeout=600)
return True
3. Rotate secrets safely and avoid weak sources
Store secrets outside source code, for example using environment variables or a secrets manager, and rotate them periodically. In settings, load securely:
import os
from django.conf import settings
settings.SHARED_SECRET = os.environ.get('HMAC_SHARED_SECRET')
if not settings.SHARED_SECRET:
raise ImproperlyConfigured('HMAC_SHARED_SECRET environment variable is required')
By combining enforced verification, inclusion of all request-affecting data, constant-time comparison, and replay protection, you eliminate the authentication gap that makes HMAC ineffective. MiddleBrick’s scans validate that signatures are checked across all methods and that findings map to frameworks such as OWASP API Top 10 and SOC2, providing prioritized remediation guidance without claiming to fix or block requests.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |