HIGH cors wildcarddjangohmac signatures

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 IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Can I use a CORS wildcard if my API does not use HMAC signatures?
You can use a wildcard for truly public endpoints that do not rely on authentication or HMAC. However, once you add HMAC or any credentialed check, you should restrict origins to prevent browsers from sending authenticated requests from arbitrary sites.
What is a safer alternative to Hmac Signatures for Django APIs?
For many use cases, short-lived JWTs with strict CORS policies and HTTPS are a practical alternative. Ensure tokens are scoped, include expiration, and are validated server-side, and never pair wildcard CORS with token-based auth.