Credential Stuffing in Fastapi with Hmac Signatures
Credential Stuffing in Fastapi with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Credential stuffing is an automated attack where valid credentials from other breaches are tried against an API to gain unauthorized access. When Fastapi endpoints rely on Hmac signatures for authentication without additional protections, the attack surface shifts to the signature and replay aspects rather than the plaintext password, but the risk of unauthorized access remains.
Hmac signatures are typically computed over selected parts of a request—such as the HTTP method, path, selected headers, and a timestamp—to prove possession of a shared secret without transmitting the secret itself. In Fastapi, a common pattern is to require a client to send a signature in a custom header (e.g., X-API-Signature) and to recompute the Hmac on the server using the same algorithm and shared secret. If the server validates the signature correctly but does not bind the signature tightly to a per-request nonce or timestamp, and does not enforce strict replay and rate controls, attackers can replay captured requests or perform systematic trials with different usernames while keeping the signature valid for a window of time.
During credential stuffing, the attacker iterates over many username and password pairs. With Hmac, the attacker does not need to know the shared secret if they can reuse a valid signature for a known payload. For example, if the signature covers the request body username but not the password, an attacker might reuse a captured signature for one username while substituting another username in the body, and the server may still accept the request if the signature verification does not include the exact request content that identifies the username. Additionally, if Fastapi does not enforce strict timestamp validation or allows large clock-skew windows, attackers can replay requests within the validity period. The lack of per-request uniqueness (nonce) enables replay, and insufficient rate-limiting allows high-volume attempts without triggering defenses. These gaps mean that even with Hmac integrity, the endpoint can be abused for credential stuffing, especially when user enumeration is possible via timing differences or distinct error messages.
Compounded issues often include predictable or reused nonces, missing binding of the signature to the target resource identifier (e.g., user ID), and failure to tie the signature to the authentication context (such as session or token binding). In Fastapi, developers might mistakenly believe that Hmac alone prevents tampering, but without additional controls—such as strict one-time nonces, short timestamp windows, and application-level rate limiting—the Hmac layer primarily ensures integrity, not freshness or uniqueness, leaving the API vulnerable to credential stuffing through replay and systematic trial attacks.
Hmac Signatures-Specific Remediation in Fastapi — concrete code fixes
To mitigate credential stuffing and replay when using Hmac signatures in Fastapi, ensure each request includes a timestamp and a cryptographically random nonce, and bind the signature to the full request content and these values. Validate timestamps tightly (for example, within 5 minutes), reject reused nonces, and enforce rate-limiting at the endpoint or IP/user level. The following example demonstrates a secure Fastapi implementation that ties Hmac verification to method, path, timestamp, nonce, and the request body, and that rejects replays.
import time
import hmac
import hashlib
import secrets
from fastapi import Fastapi, Header, HTTPException, Request
from pydantic import BaseModel
app = Fastapi()
# In production, store this securely and rotate carefully
SHARED_SECRET = b'example-shared-secret'
# Simple in-memory store for replay protection (use a distributed cache in production)
seen_nonces = set()
CLOCK_SKEW_SECONDS = 300 # 5 minutes
class LoginRequest(BaseModel):
username: str
password: str
def verify_hmac_signature(
method: str,
path: str,
timestamp: str,
nonce: str,
body: bytes,
received_signature: str
) -> bool:
# Reject if timestamp is too old or far in the future
try:
req_time = int(timestamp)
except ValueError:
return False
now = int(time.time())
if abs(now - req_time) > CLOCK_SKEW_SECONDS:
return False
# Reject if nonce was already used
if nonce in seen_noncs:
return False
# Construct the data to sign: method + path + timestamp + nonce + body
data_to_sign = f'{method}{path}{timestamp}{nonce}'.encode() + body
expected = hmac.new(SHARED_SECRET, data_to_sign, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received_signature)
@app.post('/login')
async def login(
request: Request,
body: LoginRequest,
x_api_signature: str = Header(None, alias='X-API-Signature'),
x_api_timestamp: str = Header(None, alias='X-API-Timestamp'),
x_api_nonce: str = Header(None, alias='X-API-Nonce')
):
# Ensure all required headers are present
if not all([x_api_signature, x_api_timestamp, x_api_nonce]):
raise HTTPException(status_code=400, detail='Missing required headers')
# Verify Hmac over method, path, timestamp, nonce, and body
if not verify_hmac_signature(
method=request.method,
path=request.url.path,
timestamp=x_api_timestamp,
nonce=x_api_nonce,
body=request.body(),
received_signature=x_api_signature
):
raise HTTPException(status_code=401, detail='Invalid signature')
# Mark nonce as used to prevent replay
seen_nonces.add(x_api_nonce)
# Proceed with authentication logic (e.g., verify body.username and body.password)
# This is a placeholder for actual credential validation
return {'status': 'ok'}
Key remediation points illustrated:
- Include timestamp and nonce in the signed payload to ensure freshness and uniqueness.
- Enforce a tight timestamp window (e.g., 5 minutes) to limit replayability.
- Maintain server-side tracking of used nonces to reject replays; in distributed deployments, use a shared cache with TTL slightly longer than the timestamp window.
- Validate the signature over the full request body so that changing any parameter (including username) invalidates the signature.
- Apply rate-limiting at the API gateway or within Fastapi to limit attempts per username or IP, reducing the impact of credential stuffing even if other controls are bypassed.
For production, replace the in-memory nonce store with a persistent, TTL-backed store, use a strong secret rotation strategy, and consider adding per-user or per-IP rate limits. These Hmac-specific controls make replay and credential stuffing impractical even if attackers obtain valid request samples.