Dns Rebinding in Flask with Hmac Signatures
Dns Rebinding in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability
DNS rebinding is a client-side attack that manipulates DNS responses to make a browser send requests to an internal IP address that differs from the initially resolved hostname. In Flask applications that rely on HMAC signatures for request authentication, a subtle misconfiguration can allow a malicious page to forge valid signed requests to the Flask backend without needing to know the signing key.
Consider a Flask endpoint that validates an HMAC signature in a request header (e.g., X-Signature) computed over a canonical string that includes the HTTP method, path, and body. If the endpoint does not enforce strict host or origin checks and accepts requests where the Host header can be controlled or influenced by an attacker, a crafted HTML page can perform a rebinding sequence:
- The victim visits attacker.com, which resolves to a public IP under attacker control.
- JavaScript in that page rapidly switches the DNS target for a hostname (e.g., api.trusted.com) between the attacker’s server and an internal service IP (e.g., 192.168.1.10).
- At the moment the DNS points to the internal IP, the browser sends a request to that internal address with headers and cookies that would normally be sent only to api.trusted.com.
- If the Flask app uses a weak canonicalization strategy for the signed string (e.g., including the raw Host header or not binding the signature to a server-side notion of origin), the HMAC validation may pass because the attacker can precompute or leak the effective data being signed.
- The request appears authenticated, and the Flask endpoint may perform an action (reading internal resources, changing settings) because the signature matches under the attacker-controlled conditions.
This becomes particularly dangerous when the Flask app’s HMAC implementation does not explicitly bind the signature to a fixed server-side scope such as a known service identifier or a strict set of request attributes. For example, if the signed payload includes the HTTP method and path but omits a canonical host or a nonce that is verified server-side, an attacker can flip DNS to reach internal endpoints and present a valid signature for a benign-looking request. The same-origin policy in browsers does not prevent this because the request is sent to a different IP, not a different origin in terms of hostname, and cookies or authorization headers may still be included depending on withCredentials settings.
In an automated scan with middleBrick, such a misconfiguration can be surfaced as a BOLA/IDOR or Property Authorization finding when the scanner detects that a signed endpoint does not adequately isolate requests by origin or network context. The scanner’s LLM/AI Security checks may also flag the absence of server-side origin binding as a risk for prompt injection-like confusion in API handling, even though this is a web boundary issue.
Hmac Signatures-Specific Remediation in Flask — concrete code fixes
To mitigate DNS rebinding when using HMAC signatures in Flask, you must bind the signature verification to a strict server-side notion of the expected request target and reject requests that do not match that target. Below are concrete, working examples that demonstrate a safer pattern.
First, define a helper to compute and verify HMAC using a strong key and SHA-256, and include a canonical representation that incorporates a fixed service identifier and the request path, while explicitly checking the HTTP method and rejecting mismatched hosts.
import hmac
import hashlib
import time
from flask import Flask, request, abort
app = Flask(__name__)
SECRET_KEY = b'your-secure-secret-key'
SERVICE_ID = 'api.trusted.com'
# Canonical string: method|service_id|path|timestamp|body
# Server side rejects requests with stale timestamps (e.g., >2 minutes)
def compute_hmac(method, service_id, path, timestamp, body_bytes):
message = f'{method}|{service_id}|{path}|{timestamp}|'.encode() + body_bytes
return hmac.new(SECRET_KEY, message, hashlib.sha256).hexdigest()
def verify_hmac(req):
signature = req.headers.get('X-Signature')
timestamp = req.headers.get('X-Timestamp')
if not signature or not timestamp:
return False
# Reject requests with significant clock skew
now = int(time.time())
if abs(now - int(timestamp)) > 120:
return False
# Ensure the service identifier matches the server’s expectation
if req.headers.get('X-Service-ID') != SERVICE_ID:
return False
# Use the raw path without query params for canonicalization
path = req.path
body = req.get_data(as_text=False)
expected = compute_hmac(req.method, SERVICE_ID, path, timestamp, body)
return hmac.compare_digest(signature, expected)
@app.route('/protected', methods=['POST'])
def protected():
if not verify_hmac(request):
abort(401, 'Invalid signature')
return 'OK'
Key remediation points:
- Include a service identifier (e.g., X-Service-ID) in both the signed payload and the server-side check to prevent a rebinding attack from reaching an internal service that echoes or accepts a different host header.
- Do not rely on the request’s Host header for signature computation or validation; instead, use a fixed server-side constant for the service ID.
- Enforce strict timestamp validation to prevent replay across DNS changes and to reduce the window for redirection attacks.
- Always use hmac.compare_digest to avoid timing attacks when comparing signatures.
For broader protection, combine this approach with Flask’s before_request hook to reject requests where the resolved destination does not match an allowlist, and ensure that CORS and Referrer-Policy headers are set appropriately to reduce the browser’s role in rebinding. middleBrick’s scans can validate that your HMAC implementation includes these constraints and does not rely on mutable headers subject to DNS manipulation.