HIGH integrity failuresflaskhmac signatures

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_id and acted_by, preventing attackers from changing one without breaking the signature.
  • sort_keys=True ensures deterministic serialization so the signature remains consistent between sender and receiver.
  • Authorization is explicitly checked by comparing acted_by with the authenticated user context, closing the BOLA/IDOR vector that would otherwise exist even with a valid HMAC.
  • The use of hmac.compare_digest mitigates 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.

Frequently Asked Questions

Why does including only partial fields in the HMAC payload create an integrity failure?
If the signature does not cover all security-critical fields such as user identifiers or permissions, an attacker can modify the excluded fields without invalidating the signature. This breaks integrity and enables BOLA/IDOR because the server trusts the request after signature verification, even though the payload has been tampered with in unchecked areas.
How can replay attacks still occur when HMAC signatures include timestamps?
Replay attacks can occur if the application verifies the signature but does not enforce strict timestamp windows or nonce uniqueness. Without rejecting expired timestamps or re-used nonces, an attacker can resend a previously signed request within the allowed time window, and the server will accept it as valid due to intact signature and timestamp checks.