Integrity Failures in Flask with Hmac Signatures
Integrity Failures in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability
An integrity failure occurs when a server does not adequately verify that data has not been altered in transit. In Flask applications that rely on HMAC signatures, this typically means the application validates the signature but does not enforce strict checks on the payload structure, timing, or scope of authorization. For example, a developer may use hmac.compare_digest to compare signatures but still process a request if the signature matches, even when the payload contains elevated permissions or cross-user identifiers.
Consider an endpoint that accepts a JSON payload with an item_id and an acted_by field. If the HMAC is computed only over the item_id and a shared secret, an attacker who knows or guesses another user’s ID can change acted_by without altering the signature. Because the server only verifies the signature and not the authorization relationship between the authenticated user and the target ID, this becomes a BOLA/IDOR that stems from integrity failure in the HMAC verification flow.
Another common pattern is including metadata like timestamps or nonces in the signature. If the application verifies the signature but does not validate the timestamp window or nonce uniqueness, an attacker may replay a previously signed request within the allowed time window. In Flask, this can happen when the verification logic does not reject reused nonces or expired timestamps, effectively bypassing replay protections that the HMAC scheme was meant to provide.
Using OpenAPI specifications, such flawed implementations may appear as valid endpoint definitions because the spec describes accepted parameters and a security scheme based on HMAC, but does not enforce how the application validates payloads at runtime. For instance, an operationId may state that the request body must include user_id, yet the server-side HMAC verification does not bind the signature to that field. This mismatch between spec-defined expectations and runtime behavior is a root cause of integrity failures detectable by a black-box scanner that tests unauthenticated attack surfaces.
Real-world attack patterns mirror these scenarios. For example, an attacker might probe endpoints with modified user_id or role fields while keeping the HMAC valid by omitting those fields from the signed data. This maps to common weaknesses enumerated in the OWASP API Top 10, such as broken object level authorization, and can be reflected in findings related to BOLA/IDOR and Property Authorization. Insecure use of HMAC can also interact poorly with other checks, such as Input Validation, if the server does not enforce strict schema validation before computing or verifying signatures.
Because middleBrick scans the unauthenticated attack surface and compares runtime behavior against the OpenAPI spec, it can highlight situations where HMAC-based integrity controls are present but insufficiently enforced. The scanner does not alter or block traffic; it reports findings with severity ratings and remediation guidance to help developers tighten the verification logic and ensure that HMAC truly protects the integrity and authorization intent of each request.
Hmac Signatures-Specific Remediation in Flask — concrete code fixes
To remediate integrity failures when using HMAC signatures in Flask, you must ensure that the signature covers all security-critical fields and that verification includes strict authorization checks. Below are two concrete examples: one vulnerable pattern and one corrected pattern.
Vulnerable Pattern
This example computes the HMAC over only the item_id, allowing an attacker to modify acted_by if that field is not included in the signed payload.
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
SHARED_SECRET = b'super-secret-key'
@app.route('/act', methods=['POST'])
def act():
data = request.get_json()
item_id = data.get('item_id')
acted_by = data.get('acted_by')
received_sig = request.headers.get('X-Signature')
payload = str(item_id).encode('utf-8')
expected_sig = hmac.new(SHARED_SECRET, payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected_sig, received_sig):
return jsonify({'error': 'invalid signature'}), 401
# No check that acted_by is tied to the authenticated user
return jsonify({'status': 'processed', 'item_id': item_id, 'acted_by': acted_by})
Corrected Pattern
This example includes both item_id and acted_by in the signed payload and validates the relationship before processing the request.
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
app = Flask(__name__)
SHARED_SECRET = b'super-secret-key'
@app.route('/act', methods=['POST'])
def act():
data = request.get_json()
item_id = data.get('item_id')
acted_by = data.get('acted_by')
received_sig = request.headers.get('X-Signature')
# Include all security-critical fields in the signed payload
payload = json.dumps({'item_id': item_id, 'acted_by': acted_by}, sort_keys=True).encode('utf-8')
expected_sig = hmac.new(SHARED_SECRET, payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected_sig, received_sig):
return jsonify({'error': 'invalid signature'}), 401
# Enforce authorization: ensure acted_by matches the authenticated user context
# In a real app, you would obtain the authenticated user from your auth layer
authenticated_user = 'user_123' # placeholder for actual user resolution
if acted_by != authenticated_user:
return jsonify({'error': 'forbidden: acted_by mismatch'}), 403
return jsonify({'status': 'processed', 'item_id': item_id, 'acted_by': acted_by})
Key improvements in the corrected pattern:
- The signature is computed over a canonical JSON representation that includes both
item_idandacted_by, preventing attackers from changing one without breaking the signature. sort_keys=Trueensures deterministic serialization so the signature remains consistent between sender and receiver.- Authorization is explicitly checked by comparing
acted_bywith the authenticated user context, closing the BOLA/IDOR vector that would otherwise exist even with a valid HMAC. - The use of
hmac.compare_digestmitigates timing attacks on signature comparison.
These changes align with secure coding practices for HMAC-based integrity and help ensure that the signature protects the full scope of the request, not just a subset of parameters.