Xss Cross Site Scripting in Flask with Hmac Signatures
Xss Cross Site Scripting in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Cross-site scripting (XSS) in Flask applications that use HMAC signatures can still occur when applications treat signed data as inherently safe and embed it directly into HTML or JavaScript. A common pattern is to generate a signed token or payload on the server using Flask, then embed that payload in a page (e.g., in a hidden input, a data attribute, or a serialized JSON blob) and later read it client-side. If the client-side code reconstructs objects or executes values without validating or sanitizing them, an attacker may be able to inject malicious script even though the data is signed.
For example, an application might sign a user preference or a redirect target using HMAC and include the signed blob in a form. If the application later deserializes and uses a field from that blob in an unsafe way (e.g., passing it to eval, using it to construct JavaScript, or inserting it into the DOM with innerHTML), the signature does not prevent unsafe interpretation of the content. The signature ensures integrity and origin, but it does not enforce safe output encoding or context-aware escaping. This means XSS can arise from how the application uses the verified data rather than from a weakness in the signature algorithm.
Another scenario involves JSON responses that include signed fields and are consumed by JavaScript frameworks. If a server embeds signed user-controlled strings into a script block or into event handlers, and the client-side code inserts them without escaping, reflected or stored XSS can occur. Attackers may also attempt to trick the server into signing malicious payloads via open redirects or parameter confusion, then persuade a victim to execute the signed content. Because HMAC signatures prevent tampering but not malicious content supplied by the signer, developers must treat all data—signed or not—as untrusted in HTML, attribute, and script contexts.
Hmac Signatures-Specific Remediation in Flask — concrete code fixes
Remediation centers on strict separation between integrity-protected data and safe output encoding, and never using verified data in contexts that require HTML or JavaScript parsing. Use context-aware escaping when inserting any data into HTML, JavaScript, URLs, or CSS, regardless of whether it is signed.
On the server, keep HMAC usage minimal and focused on integrity-sensitive scenarios such as callback tokens or state parameters. Below are two concrete Flask examples: one showing unsafe usage and one showing a secure pattern with signed data.
Insecure example to avoid
import json
import hmac
import hashlib
from flask import Flask, request, make_response
app = Flask(__name__)
SECRET = b'super-secret-key'
@app.route('/set-preference')
def set_preference():
user_id = request.args.get('user_id', '')
preference = request.args.get('preference', '')
payload = json.dumps({'user_id': user_id, 'preference': preference})
signature = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()
# Unsafe: embedding signed payload into HTML; later JS may use innerHTML
html = f'Set'
return htmlThis approach embeds a JSON blob into HTML attributes. Even though the blob is signed, if client-side JavaScript reads the attribute and does element.dataset.preference and then inserts it into the DOM without escaping, XSS is possible. The signature does not mitigate unsafe DOM usage.
Secure remediation with HMAC in Flask
import json
import hmac
import hashlib
from flask import Flask, request, escape, make_response
app = Flask(__name__)
SECRET = b'super-secret-key'
def verify_signature(payload: str, sig: str) -> bool:
expected = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
@app.route('/set-preference-safe')
def set_preference_safe():
user_id = escape(request.args.get('user_id', ''))
preference = escape(request.args.get('preference', ''))
# Build a safe representation for storage or non-JavaScript use
payload = json.dumps({'user_id': user_id, 'preference': preference})
signature = hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()
# Return JSON response; client must still escape on insertion
return {'payload': payload, 'sig': signature}
@app.route('/profile')
def profile():
# Example of safe reconstruction and escaping for HTML context
user_id = escape(request.args.get('user_id', ''))
preference = escape(request.args.get('preference', ''))
safe_html = f'User: {user_id}, Preference: {preference}'
return safe_htmlKey practices:
- Always escape with Flask’s
escape(or use templates with autoescape) when inserting data into HTML, and apply additional JavaScript-safe escaping (e.g., JSON serialization with proper content-type) when sending data for client-side use. - Use
hmac.compare_digestto avoid timing attacks when verifying signatures. - Keep signed payloads to integrity-critical uses (tokens, state, callback URLs) and avoid placing raw verified data into dangerous contexts such as
eval,setTimeout(string), orinnerHTML. - Apply the same-origin and signature checks on the server, and validate expectations (e.g., user_id format) before using any data, signed or not.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |