HIGH cors wildcardflaskhmac signatures

Cors Wildcard in Flask with Hmac Signatures

Cors Wildcard in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability

A CORS wildcard (Access-Control-Allow-Origin: *) combined with HMAC-signed requests in a Flask API can unintentionally expose a trust boundary bypass. In a typical HMAC setup, a client signs a request (often including a timestamp and nonce) using a shared secret, and the server verifies the signature before processing the request. When the server responds with a wildcard CORS header, any origin can read the response, including unauthorized web pages that should not be allowed to observe authenticated results.

This becomes a vulnerability when the HMAC scheme relies on the same-origin policy to limit which frontends can see responses. If a browser-based client loads a page from an attacker-controlled site, that page can make XMLHttpRequest or fetch calls to the HMAC-signed endpoint. Because the endpoint responds with a wildcard, the attacker’s page can read the response data, effectively leaking information that the HMAC was meant to protect by confirming validity or exposing business logic details.

In Flask, this often occurs when developers set Access-Control-Allow-Origin: * globally while also using custom headers or authorization via HMAC. Wildcards are incompatible with credentials and many security-sensitive headers. Even if the HMAC verification is implemented server-side, the lack of an explicit origin check on the server means the browser delivers the response to any JavaScript, undermining the intended isolation. Additionally, if preflight requests are not strictly scoped, a wildcard can allow methods or headers that should be restricted, further increasing risk.

Consider an endpoint that returns sensitive account information after verifying an HMAC signature composed of a timestamp, client ID, and a secret. If CORS allows any origin, a malicious site can embed an image tag or script that initiates the request in the user’s authenticated browser session (assuming cookies or tokens are sent), read the response via JavaScript, and exfiltrate data. While HMAC prevents tampering, it does not prevent unintended disclosure when CORS is misconfigured, and the server must validate the Origin header explicitly rather than relying on a wildcard.

To detect this during a scan, middleBrick evaluates whether CORS headers include a wildcard while also inspecting whether the application applies origin allowlists and whether HMAC-signed endpoints include strict referer or origin checks. The scanner checks runtime behavior alongside OpenAPI specifications to identify mismatches between declared CORS policy and actual endpoint behavior, highlighting cases where a wildcard conflicts with security-sensitive signatures.

Hmac Signatures-Specific Remediation in Flask — concrete code fixes

Remediation centers on removing the wildcard and enforcing an allowlist of trusted origins, combined with consistent HMAC verification in Flask. The server should validate the Origin header against a strict list and set Access-Control-Allow-Origin to the requesting origin only when it is permitted. Below are concrete code examples demonstrating a secure pattern.

HMAC verification utility

Implement a reusable function to verify the HMAC signature. This example uses SHA256 and expects the client to send a timestamp, a nonce, and the signature in headers.

import hashlib
import hmac
import time
def verify_hmac_signature(payload: str, signature: str, secret: str) -> bool:
    """Verify that the provided signature matches HMAC-SHA256 of the payload."""
    mac = hmac.new(secret.encode('utf-8'), payload.encode('utf-8'), hashlib.sha256)
    expected = mac.hexdigest()
    return hmac.compare_digest(expected, signature)

Flask endpoint with strict CORS and HMAC

This endpoint validates an HMAC signature and only responds if the origin is allowed. It avoids wildcard usage and returns appropriate CORS headers for the specific origin.

from flask import Flask, request, jsonify

app = Flask(__name__)

ALLOWED_ORIGINS = {"https://trusted.example.com", "https://app.example.com"}
HMAC_SECRET = "super-secret-shared-key"

@app.before_request
def cors_middleware():
    origin = request.headers.get("Origin")
    if origin in ALLOWED_ORIGINS:
        # Reflect the origin instead of using a wildcard
        @app.after_request
def set_cors_headers(response):
    origin = request.headers.get("Origin")
    if origin in ALLOWED_ORIGINS:
        response.headers["Access-Control-Allow-Origin"] = origin
        response.headers["Access-Control-Allow-Methods"] = "POST, OPTIONS"
        response.headers["Access-Control-Allow-Headers"] = "Content-Type, X-API-Signature, X-Timestamp, X-Nonce"
    return response

@app.route("/api/account", methods=["POST", "OPTIONS"])
def account_info():
    if request.method == "OPTIONS":
        # Preflight handling
        return "", 200

    signature = request.headers.get("X-API-Signature")
    timestamp = request.headers.get("X-Timestamp")
    nonce = request.headers.get("X-Nonce")

    if not all([signature, timestamp, nonce]):
        return jsonify({"error": "missing_auth"}), 400

    # Basic replay protection: ensure timestamp is recent (e.g., within 5 minutes)
    try:
        ts = int(timestamp)
    except ValueError:
        return jsonify({"error": "invalid_timestamp"}), 400

    if abs(time.time() - ts) > 300:
        return jsonify({"error": "stale_request"}), 400

    payload = f"{timestamp}{nonce}{request.get_data(as_text=True)}"
    if not verify_hmac_signature(payload, signature, HMAC_SECRET):
        return jsonify({"error": "invalid_signature"}), 403

    # Proceed with business logic
    return jsonify({"account": {"id": 123, "name": "Alice"}})

Key remediation practices

  • Never use a CORS wildcard when serving responses to authenticated or signed requests.
  • Validate the Origin header server-side against an explicit allowlist and reflect it in Access-Control-Allow-Origin.
  • Include HMAC-signed headers (timestamp, nonce, signature) and enforce freshness to mitigate replay attacks.
  • Handle OPTIONS preflight responses explicitly and limit allowed methods and headers to only what is necessary.

By combining strict origin validation with robust HMAC verification, Flask APIs can safely use signed requests while avoiding information disclosure via CORS misconfiguration. middleBrick’s scans highlight such misalignments between CORS policy and runtime behavior, helping teams enforce precise allowlists instead of relying on wildcards.

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

Why is using a CORS wildcard dangerous when HMAC signatures are in place?
A wildcard allows any origin to read the response, which can expose information that HMAC was intended to protect. Even with valid signatures, an attacker’s page can make requests from an authenticated user’s browser and read the results if the server reflects a wildcard in Access-Control-Allow-Origin.
How can I securely configure CORS in Flask with HMAC-signed endpoints?
Use an explicit allowlist of trusted origins, set Access-Control-Allow-Origin to the requesting origin only when it’s on the allowlist, and avoid wildcards. Combine this with HMAC verification of timestamps, nonces, and signatures to ensure requests are authentic and not subject to cross-origin leakage.