Dns Cache Poisoning in Fastapi with Hmac Signatures
Dns Cache Poisoning in Fastapi with Hmac Signatures — how this specific combination creates or exposes the vulnerability
DNS cache poisoning can intersect with FastAPI applications that rely on external services, especially when those services are discovered or addressed via DNS. If a FastAPI app uses DNS-based service discovery (for example, resolving a backend hostname at startup or runtime) and does not validate or pin resolved addresses, an attacker who can poison the DNS cache may redirect the app to a malicious host. When HMAC signatures are used to authenticate requests or webhook notifications, the security of the signature mechanism depends on the integrity of the endpoint reached. If the resolved hostname changes due to cache poisoning, a FastAPI client may unknowingly send requests or verify signatures against an attacker-controlled server.
Consider a scenario where a FastAPI client resolves api.partner.example.com to an IP, caches it, and uses that IP to call a service. If the cache is poisoned to point to an attacker server that also presents a valid TLS certificate (or is accepted for other reasons), the client may trust responses from the malicious server. If the client then verifies HMAC signatures on responses to confirm authenticity, the verification may still pass if the attacker correctly implements the shared secret logic. The vulnerability arises when the signature is meant to guarantee the source and integrity of a response tied to a specific, trusted endpoint; DNS poisoning undermines that assumption by altering the endpoint the client believes it is communicating with.
This combination is notable because HMAC signatures protect integrity and origin, but they do not by themselves prevent the client from connecting to the wrong host if DNS is compromised. In FastAPI, this can manifest when using HTTP clients like httpx or requests with custom transports or when integrating with service meshes or external APIs that rely on dynamic DNS. If the application does not validate server identity beyond signatures (for example, by pinning certificates or using strict hostname verification), an attacker who successfully poisons DNS may intercept or manipulate traffic without invalidating the HMAC checks, provided they can replicate or predict signature behavior on their own server.
To detect related risks, middleBrick scans can surface findings tied to input validation and insecure consumption practices, including anomalies in how external endpoints are resolved and used. Such scans help highlight weaknesses in the trust chain that spans DNS resolution, transport security, and signature verification. The scan results can prioritize issues where signature-based authentication coexists with dynamic or unverified host resolution, guiding remediation toward more robust endpoint binding and validation strategies.
Hmac Signatures-Specific Remediation in Fastapi — concrete code fixes
Defending against DNS cache poisoning when using HMAC signatures in FastAPI requires ensuring that the endpoint identity is verified independently of DNS, and that signatures cover or account for the target host or a strong canonical identifier. Below are concrete remediation steps with code examples.
- Pin the resolved IP or use HTTPS with certificate pinning for critical external calls. Instead of relying solely on DNS at runtime, resolve the hostname once during startup, store the expected IP or certificate fingerprint, and validate it on each connection. This prevents an attacker from redirecting traffic by poisoning the cache after startup.
import httpx
from starlette.applications import Starlette
from starlette.responses import JSONResponse
import ssl
EXPECTED_FINGERPRINT = "SHA256:ABCD...1234" # certificate fingerprint
async def get_verified_client():
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False # we do custom verification
ssl_context.verify_mode = ssl.CERT_REQUIRED
async with httpx.AsyncClient(verify=ssl_context) as client:
# custom logic to verify certificate fingerprint against EXPECTED_FINGERPRINT
return client
app = Starlette()
@app.route("/call-partner")
async def call_partner():
async with await get_verified_client() as client:
# Use a fixed, vetted endpoint or an IP literal if possible
resp = await client.get("https://192.0.2.1:8443/resource")
return JSONResponse({"status": resp.status_code, "body": resp.text})
- Include the request target (host and path) or a canonical representation in the HMAC payload. When generating and verifying HMACs, incorporate the hostname or a trusted identifier so that a signature computed for one host is invalid if presented to another. This binds the signature to a specific endpoint and reduces the impact of DNS redirection.
import hmac
import hashlib
import time
from fastapi import FastAPI, Request, Header, HTTPException
app = FastAPI()
SECRET = b"super-secret-shared-key"
def generate_hmac(payload: str, host: str) -> str:
message = f"{host}|{payload}".encode()
return hmac.new(SECRET, message, hashlib.sha256).hexdigest()
@app.post("/webhook")
async def webhook(
request: Request,
x_signature: str = Header(None),
x_host: str = Header(None)
):
if not x_signature or not x_host:
raise HTTPException(status_code=400, detail="Missing signature headers")
body = await request.body()
expected = generate_hmac(body.decode(), x_host)
if not hmac.compare_digest(expected, x_signature):
raise HTTPException(status_code=401, detail="Invalid signature")
return {"status": "ok"}
- Use explicit host overrides or a strict allowlist when making outbound calls from FastAPI. If you must call a logical hostname, resolve it at startup, validate it against an allowlist, and use the resolved address for subsequent calls. Avoid runtime DNS re-resolution for sensitive operations unless you re-validate the endpoint identity and signature context.
import httpx
from fastapi import FastAPI, HTTPException
import ssl
app = FastAPI()
ALLOWED_HOSTS = {"api.partner.example.com": "192.0.2.1"}
CERT_FINGERPRINT = "SHA256:ABCD...1234"
async def build_client(host: str):
if host not in ALLOWED_HOSTS:
raise ValueError("Host not allowed")
ip = ALLOWED_HOSTS[host]
ssl_context = ssl.create_default_context()
ssl_context.verify_mode = ssl.CERT_REQUIRED
# optionally verify certificate fingerprint here
return httpx.AsyncClient(base_url=f"https://{ip}:8443", verify=ssl_context)
@app.get("/data")
async def get_data():
host = "api.partner.example.com"
async with build_client(host) as client:
resp = await client.get("/resource")
if resp.status_code != 200:
raise HTTPException(status_code=502, detail="Upstream error")
return {"data": resp.text}
These examples emphasize binding HMAC verification to a concrete, validated endpoint identity and avoiding unchecked DNS resolution at runtime for security-critical operations. middleBrick can surface related input validation and insecure consumption findings to highlight where such mitigations are most needed.