Clickjacking in Flask with Hmac Signatures
Clickjacking in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side attack that tricks a user into interacting with a hidden or disguised UI element inside an embedded page. In Flask, developers sometimes use HMAC signatures to protect the integrity of state-changing requests (such as form submissions), for example by signing an action identifier or a timestamp and validating it on the server. Relying on HMAC for request integrity does not prevent clickjacking, because the attack occurs in the browser: an attacker can embed your Flask app in an iframe and overlay invisible controls or use UI redressing to make the user unknowingly trigger those actions. Even if a request carries a valid HMAC, the user did not intentionally perform the action, and the server-side signature check only verifies that the request was formed correctly, not that it was initiated intentionally by the user.
If your Flask app embeds pages in iframes or exposes endpoints that perform sensitive operations without additional anti-CSRF or framing protections, clickjacking can still occur. HMAC signatures applied to request parameters are useful for detecting tampering, but they do not enforce same-origin context or prevent the browser from loading the page inside an invisible frame. For example, an attacker can craft a malicious page that loads /transfer?amount=1000&to=attacker&hmac=VALID_SIGNATURE inside a transparent iframe and use CSS to position a button over a visible decoy. When the user clicks the decoy, the forged request is sent with a valid HMAC, and the server processes it as legitimate. This shows why HMAC alone cannot replace frame-busting, X-Frame-Options, or Content-Security-Policy frame-ancestors directives.
Additionally, if your Flask application exposes an unauthenticated endpoint that issues sensitive actions and relies only on HMAC for protection, it may be reachable via an automated or social attack vector. MiddleBrick’s scans include checks for unauthenticated endpoints and unsafe consumption patterns, which can surface routes that accept HMAC-signed requests without requiring user interaction verification or origin checks. The presence of a valid HMAC does not imply user consent or intent, and it does not mitigate risks from cross-origin embedding. Therefore, HMAC signatures should be combined with anti-CSRF tokens, strict Referrer-Policy, and CSP frame-ancestors rules to reduce the likelihood of successful clickjacking.
Hmac Signatures-Specific Remediation in Flask — concrete code fixes
To address clickjacking and related UI redressing risks in Flask, use defense-in-depth: combine HMAC-based integrity checks with anti-CSRF tokens and HTTP response headers. The following examples show a minimal Flask app that signs actions with HMAC, validates the signature, and adds security headers to mitigate framing attacks.
HMAC-signed form submission in Flask
Server-side:
import time
import hmac
import hashlib
import secrets
from flask import Flask, request, render_template_string, abort, make_response
app = Flask(__name__)
SECRET_KEY = secrets.token_bytes(32) # store securely, e.g., from environment
def generate_hmac(payload: str, secret: bytes) -> str:
return hmac.new(secret, payload.encode('utf-8'), hashlib.sha256).hexdigest()
@app.route('/')
def index():
nonce = secrets.token_hex(16)
timestamp = str(int(time.time()))
payload = f'{nonce}:{timestamp}'
signature = generate_hmac(payload, SECRET_KEY)
form_html = '''
'''
return render_template_string(form_html, nonce=nonce, timestamp=timestamp, signature=signature)
@app.route('/action', methods=['POST'])
def action():
nonce = request.form.get('nonce')
timestamp = request.form.get('timestamp')
received_hmac = request.form.get('hmac')
if not all([nonce, timestamp, received_hmac]):
abort(400, 'Missing parameters')
# Prevent replay: reject old timestamps (e.g., >2 minutes)
if abs(int(time.time()) - int(timestamp)) > 120:
abort(400, 'Request expired')
payload = f'{nonce}:{timestamp}'
expected_hmac = generate_hmac(payload, SECRET_KEY)
if not hmac.compare_digest(expected_hmac, received_hmac):
abort(403, 'Invalid signature')
# Additional anti-CSRF checks can be added here (referrer/origin validation)
return 'Action completed'
Client-side protections:
# Add these response headers in Flask or via a proxy
@app.after_request
def set_security_headers(response):
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Content-Security-Policy'] = "frame-ancestors 'none'"
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
return response
The HMAC example above ensures that the form payload (nonce and timestamp) cannot be altered without detection. However, to prevent clickjacking, you must also deny framing via X-Frame-Options: DENY or a strict CSP frame-ancestors 'none'. Use a unique nonce per session and short-lived timestamps to limit replay risk. Combine these measures with server-side checks for Origin or Referer where appropriate, and perform regular scans (e.g., with the middlebrick CLI) to detect unauthenticated or overly permissive endpoints.
Frequently Asked Questions
Do HMAC signatures prevent clickjacking on their own?
How can I test that my Flask app is not vulnerable to clickjacking?
iframe and verifying that it is blocked by your security headers.