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
Originheader server-side against an explicit allowlist and reflect it inAccess-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 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 |
Frequently Asked Questions
Why is using a CORS wildcard dangerous when HMAC signatures are in place?
Access-Control-Allow-Origin.How can I securely configure CORS in Flask with HMAC-signed endpoints?
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.