Cors Wildcard in Django with Hmac Signatures
Cors Wildcard in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
In Django, a CORS wildcard (Access-Control-Allow-Origin: *) combined with HMAC-signed requests can unintentionally expose authenticated or sensitive endpoints to any origin. When HMAC signatures are used, the client typically adds a signature derived from a shared secret plus selected request attributes (method, path, timestamp, body). If the server validates the HMAC and then responds with a wildcard Access-Control-Allow-Origin, any website can make authenticated-looking requests on behalf of users who have credentials, provided those requests include the correct HMAC.
The vulnerability arises because CORS is a browser-enforced mechanism; non-browser clients are not restricted by it. However, browsers will include cookies or authorization headers in cross-origin requests when credentials are allowed. If the server trusts the HMAC and does not also enforce a strict allowlist of origins, a malicious site can craft requests that carry valid signatures (e.g., by obtaining a valid timestamp and nonce from a legitimate API call) and the server will process them as legitimate. This effectively bypasses origin-based access control.
Consider a Django endpoint that uses HMAC authentication via a custom header, such as X-API-Signature, and also serves CORS responses with a wildcard. An attacker’s page can issue a GET request to the endpoint, first querying a benign endpoint to obtain a fresh timestamp and nonce, then compute the HMAC and send it to the target endpoint. Since the server validates the signature and returns *, the browser will allow the page to read the response if credentials are included, leading to unauthorized data access or unauthorized actions.
Additionally, if preflight requests (OPTIONS) respond with Access-Control-Allow-Origin: * and also indicate allowed methods and headers that include HMAC-related headers, the browser may cache this and allow subsequent authenticated requests from any origin. This misconfiguration is subtle because the API may assume HMAC alone is sufficient, not realizing that CORS relaxes origin constraints in the browser context.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
To remediate, enforce a strict CORS allowlist and ensure HMAC validation is coupled with origin checks. Do not use a wildcard for endpoints that rely on HMAC or any form of authentication. Below is a concrete Django example using django-cors-headers and a custom HMAC validation view.
import hmac
import hashlib
import time
import json
from django.http import JsonResponse
from django.views.decorators.http import require_GET
from django.conf import settings
from corsheaders.defaults import default_headers
def verify_hmac(request):
# Example HMAC verification using timestamp, nonce, and method
timestamp = request.headers.get('X-Timestamp')
nonce = request.headers.get('X-Nonce')
signature = request.headers.get('X-API-Signature')
if not all([timestamp, nonce, signature]):
return False
# Prevent replay: reject if timestamp is older than 300 seconds
if abs(time.time() - int(timestamp)) > 300:
return False
message = f'{request.method}{request.path}{timestamp}{nonce}'.encode()
expected = hmac.new(
settings.SECRET_KEY.encode(),
message,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@require_GET
def secure_data_view(request):
if not verify_hmac(request):
return JsonResponse({'error': 'invalid signature'}, status=403)
# Business logic here
return JsonResponse({'data': 'secure'})
Configure CORS settings to restrict origins and avoid wildcards for authenticated endpoints:
# settings.py
CORS_ALLOWED_ORIGINS = [
'https://trusted.example.com',
'https://app.example.com',
]
CORS_ALLOW_CREDENTIALS = True
# Only expose necessary headers; avoid wildcard
CORS_ALLOW_HEADERS = list(default_headers) + [
'X-API-Signature',
'X-Timestamp',
'X-Nonce',
]
# Preflight cache max age can be tuned, but avoid broad caching for sensitive endpoints
CORS_PREFLIGHT_MAX_AGE = 86400
In production, combine this with Django middleware to reject requests with mismatched origins even if CORS headers are present. For endpoints that must be public, avoid HMAC-based authentication or use short-lived tokens instead. The combination of a strict origin allowlist and HMAC validation ensures that even if a signature is valid, the request is only honored from known, trusted origins.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |