HIGH replay attackflaskhmac signatures

Replay Attack in Flask with Hmac Signatures

Replay Attack in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability

A replay attack occurs when an attacker intercepts a valid request and retransmits it to reproduce the intended effect without going through the authentication logic. In Flask applications that rely on HMAC signatures to verify request integrity, a common pitfall is signing only a subset of the request components (for example, the payload) while omitting nonces or timestamps. Because HMAC is a deterministic algorithm, the same input always produces the same signature. If the server verifies the signature but does not enforce uniqueness per request, an attacker can capture a signed request—such as a payment or state-changing API call—and replay it multiple times with identical effect.

Consider a Flask route that expects a JSON body and an HMAC-SHA256 signature in a header, where the signature is computed over the raw request body. If the server only validates the signature and does not require a nonce or timestamp, an intercepted request can be replayed indefinitely. This is especially dangerous for idempotency-unsafe operations or when the application mistakenly assumes that transport-layer protections (such as TLS) prevent replay. Even with TLS, a malicious insider or a compromised proxy can capture and re-send traffic. The vulnerability is not in HMAC itself, which provides strong integrity guarantees, but in the application design that fails to bind the signature to a unique, single-use context.

Another contributing factor is poor handling of clock drift and replay windows. If a server accepts a timestamp that is too old or does not enforce a tight validity window, an attacker can slightly adjust timing to bypass freshness checks. In Flask, this can happen when developers implement custom timestamp checks without a strict window or without combining the timestamp with a nonce. The combination of HMAC signatures with missing replay protections—nonces, timestamps, or both—creates a scenario where authenticated endpoints remain vulnerable to replay, despite the presence of cryptographic integrity checks.

Hmac Signatures-Specific Remediation in Flask — concrete code fixes

To defend against replay attacks while using HMAC signatures in Flask, you must ensure each request includes a unique value (a nonce or a timestamp with sufficient precision) and that the server tracks or validates this uniqueness within an acceptable window. Below is a concrete, secure example that combines HMAC-SHA256 with a timestamp and a server-side cache of recently seen nonces to prevent replays.

import time
import hmac
import hashlib
import json
from flask import Flask, request, abort, g
import functools

app = Flask(__name__)

# Shared secret stored securely (e.g., from environment)
SECRET = b'super-secret-key-change-in-production'

# In production, replace this with a distributed cache (e.g., Redis) if you run multiple workers
seen_nonces = set()
NONCE_TTL_SECONDS = 300  # 5 minutes

def verify_hmac(request_body, received_signature):
    computed = hmac.new(SECRET, request_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, received_signature)

@app.before_request
def enforce_hmac_and_replay_protection():
    if request.method in ('POST', 'PUT', 'PATCH'):
        signature = request.headers.get('X-API-Signature')
        timestamp = request.headers.get('X-Request-Timestamp')
        nonce = request.headers.get('X-Request-Nonce')

        if not signature or not timestamp or not nonce:
            abort(400, 'Missing signature, timestamp, or nonce')

        # Enforce timestamp freshness (e.g., 5 minutes)
        try:
            ts = float(timestamp)
        except ValueError:
            abort(400, 'Invalid timestamp')

        now = time.time()
        if abs(now - ts) > 300:
            abort(400, 'Request timestamp outside allowed window')

        # Replay protection: reject previously seen nonces
        if nonce in seen_nonces:
            abort(400, 'Replay detected: nonce already used')

        # Store nonce with a cleanup strategy (simplified)
        seen_nonces.add(nonce)
        g.nonce = nonce
        g.timestamp = ts

        # Verify HMAC over the raw body
        if not verify_hmac(request.get_data(), signature):
            abort(401, 'Invalid signature')

@app.route('/api/transfer', methods=['POST'])
def transfer():
    # Business logic here; request is guaranteed fresh and authenticated
    data = request.get_json()
    return {'status': 'ok', 'received': data}, 200

if __name__ == '__main__':
    app.run(ssl_context='adhoc')

Key points in the remediation:

  • Include both a timestamp and a nonce in the signed scope. The timestamp ensures freshness, while the nonce guarantees uniqueness even if timestamps collide.
  • Compute the HMAC over the raw request body (or a canonical representation of the important fields) and compare using hmac.compare_digest to avoid timing attacks.
  • Enforce a tight timestamp window (e.g., 300 seconds) and reject requests outside this window to limit the replay window.
  • Maintain a short-lived store of recently seen nonces to detect replays. In production, use a shared cache with TTL to handle multiple workers and restarts.
  • Ensure the secret key is stored securely (environment variables or a secrets manager) and rotated periodically.

Alternative approach for frameworks that support request signing via a standard like Digest or framework-specific helpers is possible, but the principle remains: bind the signature to a unique, single-use context and validate freshness on every request.

Frequently Asked Questions

Why does including a nonce prevent replay attacks even when HMAC already ensures integrity?
HMAC guarantees that the request body has not been altered, but it does not guarantee uniqueness. A nonce ensures each request is distinct; the server tracks recently seen nonces and rejects duplicates, which stops an attacker from reusing a captured signed request.
Can relying solely on HTTPS and short timestamp windows replace nonces for replay protection?
No. TLS prevents passive eavesdropping but does not stop an active attacker who can capture and replay requests. Short timestamp windows reduce the replay window but do not prevent replays within that window; nonces provide definitive protection against any replay, regardless of timing.