HIGH replay attackfastapihmac signatures

Replay Attack in Fastapi with Hmac Signatures

Replay Attack in Fastapi 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 original effect. In Fastapi applications that use Hmac signatures for request authentication, the vulnerability arises when the server validates the signature and timestamp but does not enforce strict one-time use or freshness guarantees. Hmac signatures bind the request payload and selected headers to a shared secret, ensuring integrity and origin authenticity. However, if the server only checks that the signature is valid and that the timestamp is within an acceptable window, the same signed payload can be replayed within that window without triggering any validation failure.

Consider a Fastapi endpoint that accepts a JSON body and an X-API-Timestamp header, verifying the Hmac signature over the concatenation of timestamp and body. An attacker can capture a legitimate signed request—such as a payment initiation or a resource creation—and resend it exactly as received. Because the signature remains valid and the timestamp may still fall within the allowed skew, the server processes the operation again, potentially causing duplicate transactions or unauthorized state changes. This risk is especially pronounced when idempotency is not enforced at the application level.

The attack surface is expanded when requests include query parameters or headers that are not part of the signed string, or when the timestamp resolution is coarse (for example, seconds rather than milliseconds), making it easier to reuse a valid signature across retries. Without additional protections such as nonce tracking or strict request deduplication, Fastapi’s Hmac-based scheme can inadvertently facilitate replay despite correct cryptographic verification.

Hmac Signatures-Specific Remediation in Fastapi — concrete code fixes

To mitigate replay attacks in Fastapi when using Hmac signatures, you must ensure each signed request is unique and time-bound. This involves incorporating a nonce and a precise timestamp into the signed string, enforcing monotonicity or single-use nonces, and rejecting requests with timestamps outside a tight window. Below are concrete code examples demonstrating these protections.

First, define a utility to compute the Hmac over a canonical string that includes a client-supplied nonce and timestamp:

import hmac
import hashlib
import time
import secrets

SHARED_SECRET = b'your-256-bit-secret'

def compute_hmac(nonce: str, timestamp: str, payload: str) -> str:
    message = f'{nonce}|{timestamp}|{payload}'.encode('utf-8')
    return hmac.new(SHARED_SECRET, message, hashlib.sha256).hexdigest()

On the server side, maintain a short-lived cache of recently seen nonces to prevent reuse. Use a fixed time window (for example, 30 seconds) and enforce that the request timestamp is within that window:

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
import time

app = FastAPI()

# Simple in-memory store; consider a distributed cache in production
seen_nonces = set()
TIMESTAMP_TOLERANCE = 30  # seconds

def verify_hmac(request: Request, payload: str) -> bool:
    timestamp = request.headers.get('X-API-Timestamp')
    nonce = request.headers.get('X-API-Nonce')
    signature = request.headers.get('X-API-Signature')
    if not all([timestamp, nonce, signature]):
        return False
    # Check timestamp freshness
    now = str(int(time.time()))
    if abs(int(now) - int(timestamp)) > TIMESTAMP_TOLERANCE:
        return False
    # Reject replayed nonces
    if nonce in seen_nonces:
        return False
    expected = compute_hmac(nonce, timestamp, payload)
    if not hmac.compare_digest(expected, signature):
        return False
    # Record nonce for the duration of the tolerance window
    seen_nonces.add(nonce)
    return True

@app.post('/process')
async def process_item(request: Request):
    body = await request.body()
    body_str = body.decode('utf-8')
    if not verify_hmac(request, body_str):
        raise HTTPException(status_code=401, detail='Invalid or replayed request')
    # Safe to process
    return JSONResponse({'status': 'ok'})

For production use, replace the in-memory set with a TTL-aware store to avoid memory growth and to support multiple workers. Additionally, include HTTP method and path in the signed string if the endpoint semantics depend on the verb or route. This approach ensures that even if an attacker captures a valid Hmac-signed request, it cannot be reused outside the narrow time window and nonce constraints.

Frequently Asked Questions

What specific risks does replaying a valid Hmac-signed request cause in Fastapi?
If a Fastapi endpoint validates only the Hmac signature and timestamp but does not enforce nonce uniqueness or strict freshness, an attacker can resend a captured request within the allowed time window, causing duplicate operations such as double payments or unintended resource creation.
How should the nonce and timestamp be included in the Hmac computation to prevent replay attacks?
Include the nonce, timestamp, and the request payload in the canonical string used for the Hmac, for example: f'{nonce}|{timestamp}|{payload}'. This binds uniqueness and time sensitivity to the signature, ensuring that replayed requests fail verification or are rejected due to nonce reuse.