HIGH format stringfastapihmac signatures

Format String in Fastapi with Hmac Signatures

Format String in Fastapi with Hmac Signatures — how this specific combination creates or exposes the vulnerability

A format string issue in a FastAPI endpoint that also uses HMAC signatures arises when user-controlled input is passed directly to a formatting function such as str.format or percent-style formatting before the signature is computed or verified. If the endpoint builds a message or header using unsanitized input and then signs that message with HMAC, an attacker can supply format specifiers (e.g., %s, %n, {user_id} in Python’s .format) to alter the resulting string. This can cause information disclosure, such as leaking stack contents via %s or writing arbitrary values via %n, which may change the signed payload in subtle ways that bypass integrity checks.

Consider an endpoint that accepts a JSON body with a data field and a user_id. The server builds a signing string using string interpolation and then computes an HMAC. A vulnerable pattern might look like this:

from fastapi import FastAPI, Header, HTTPException
import hmac
import hashlib

app = FastAPI()
SECRET = b"super-secret-key"

@app.post("/action")
async def action(data: str, user_id: int, x_signature: str = Header(...)):
    # Vulnerable: user-controlled data is interpolated into the message before signing
    message = "user_id=%s data=%s" % (user_id, data)
    expected = hmac.new(SECRET, message.encode("utf-8"), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, x_signature):
        raise HTTPException(status_code=401, detail="Invalid signature")
    return {"status": "ok"}

If the attacker sends data containing format specifiers such as %s %s %s, the message string can expand to reveal memory contents, changing the computed HMAC and potentially allowing signature bypass depending on how the server handles mismatches. Even when using .format, the risk persists:

message = "user_id={uid} data={payload}".format(uid=user_id, payload=data)

Here, if data contains curly braces or formatting-like syntax, it can interfere with the intended structure, and if an attacker can predict or influence parts of the format string, they may manipulate the resulting hash verification. Because HMAC relies on exact byte-for-byte matching, any unexpected alteration to the signed string—introduced through format string behaviors—can either cause false negatives or expose runtime information during formatting errors.

In practice, this combination violates the expectation that the HMAC input is a controlled, canonical representation. An attacker who can inject format specifiers may gain read access to memory or cause side effects during string construction, undermining the integrity guarantees that HMAC is meant to provide. The vulnerability is not in HMAC itself but in how the message is constructed before the MAC is applied.

Hmac Signatures-Specific Remediation in Fastapi — concrete code fixes

To remediate format string risks when using HMAC signatures in FastAPI, ensure that user-controlled values are never interpolated or formatted into the message before signing. Instead, serialize the data in a deterministic, structured way (e.g., JSON with sorted keys) and sign the raw bytes. This eliminates any interpretation of format specifiers by the signing logic.

A secure implementation avoids string formatting entirely and uses a consistent binary representation:

import json
from fastapi import FastAPI, Header, HTTPException
import hmac
import hashlib

app = FastAPI()
SECRET = b"super-secret-key"

@app.post("/action")
async def action(data: str, user_id: int, x_signature: str = Header(...)):
    # Build a canonical JSON payload without formatting interpolation
    payload = {"user_id": user_id, "data": data}
    message = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")
    expected = hmac.new(SECRET, message, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, x_signature):
        raise HTTPException(status_code=401, detail="Invalid signature")
    return {"status": "ok"}

If you must work with preformatted strings (for legacy compatibility), treat user input strictly as data and avoid any formatting operations that interpret specifiers. Use explicit placeholders that are replaced by position or index without invoking printf-style or str.format interpolation:

import struct

# Safe: pack integers as binary, no format string interpretation
message = struct.pack("!I", user_id) + data.encode("utf-8")
expected = hmac.new(SECRET, message, hashlib.sha256).hexdigest()

Always use constant-time comparison such as hmac.compare_digest to avoid timing leaks, and ensure that the serialization method is deterministic (e.g., sorted JSON keys) so that valid signatures remain consistent across implementations. MiddleBrick scans can help detect format string patterns in API specifications and runtime tests, providing findings mapped to relevant frameworks such as OWASP API Top 10 and offering remediation guidance tailored to FastAPI implementations.

For teams using automated workflows, the middleBrick CLI allows you to scan from terminal with commands like middlebrick scan <url>, while the GitHub Action can add API security checks to your CI/CD pipeline, failing builds if risk scores exceed your configured threshold. The MCP Server enables scanning APIs directly from AI coding assistants, helping catch issues like format string misuse early in development.

Frequently Asked Questions

Can using .format or % formatting with user data break HMAC verification even if no read occurs?
Yes. If an attacker can inject format specifiers, the resulting string bytes can change, causing the HMAC to mismatch. Even if the attack does not leak memory, it can alter the signed message and bypass integrity checks.
Is it sufficient to validate the signature before checking for format strings?
No. The signature must be computed over a canonical, user-controlled-data-free representation (e.g., structured serialization). Validating after formatting does not prevent the formatting step from modifying the data that was signed.