Crlf Injection in Fastapi with Jwt Tokens
Crlf Injection in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when user-controlled data is reflected in HTTP headers without sanitization, enabling an attacker to inject additional headers or break header lines using Carriage Return (\r) and Line Feed (\n) sequences. In Fastapi, this risk can intersect with JWT token handling when a developer inadvertently uses unchecked input to construct or influence response headers, for example via custom headers, location values after redirects, or when echoing claims from a decoded token into headers.
Consider an endpoint that receives a JWT and uses a claim such as username or sub in a header without validation:
from fastapi import FastAPI, Request, Header
from typing import Optional
app = FastAPI()
@app.get("/welcome")
def welcome(x_token: Optional[str] = Header(None)):
user = {"name": "alice", "sub": "alice\r\nX-Injected: malicious"}
return {"message": f"Hello {user['name']}", "header": x_token}
If the application uses values derived from JWT claims (e.g., sub or custom claims) to build response headers, and those values are reflected without sanitization, an attacker may inject newline sequences that cause the server to append arbitrary headers. While Fastapi does not automatically reflect JWT claims into headers, developers who copy claims into headers—such as X-User-Id or Location during redirects—create an opening for CRLF injection. This becomes particularly relevant when integrating with external services or logging, where unchecked token payloads might influence header construction downstream.
Another scenario involves redirects. If a JWT contains a URL or path claim used to redirect a client, unsanitized input can lead to response splitting:
from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse
def build_redirect(target: str):
# UNSAFE: target may come from a JWT claim
return RedirectResponse(url=target)
@app.get("/go")
def go(url: str):
return build_redirect(url)
An attacker supplying url as https://example.com\r\nSet-Cookie: session=evil can inject a new header if the value is used directly. Even when JWTs are verified and their payloads consumed, failing to validate or encode values that end up in headers reintroduces CRLF injection. The presence of JWTs does not cause the flaw, but it can amplify impact by providing a trusted-seeming source for malicious input that propagates into headers.
Middleware, dependencies, or libraries that log or mirror token data into headers also widen the attack surface. For example, echoing a jti (JWT ID) into a custom header without escaping newlines can allow header manipulation. Because Fastapi relies on underlying ASGI servers to finalize headers, unsanitized input reaching the response stage can corrupt header lines, enabling cache poisoning, session fixation, or HTTP response splitting.
Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on strict validation, input sanitization, and safe construction of headers and redirects. Never directly interpolate untrusted data—especially from JWT claims—into headers or redirect URLs. Use allowlists, canonicalization, and header-safe encoding.
1) Validate and sanitize values used in headers
Ensure any data derived from a JWT claim is stripped of CRLF sequences before being used in a header. For string values that must be reflected, remove or replace \r and \n:
import re
from fastapi import FastAPI, Header
app = FastAPI()
SAFE_HEADER_RE = re.compile(r"[^\x20-\x7e\x80-\xff]*") # basic printable ASCII
def sanitize_header_value(value: str) -> str:
# Remove CR and LF to prevent header injection
return value.replace("\r", "").replace("\n", "")
@app.get("/profile")
def profile(x_token: str = Header(...)):
# Assume payload includes a 'username' claim that is untrusted
username = "alice\r\nX-Injected: malicious"
safe_username = sanitize_header_value(username)
return {"X-User": safe_username}
2) Use built-in redirect safety and avoid dynamic URLs from claims
When redirecting, prefer relative paths or validated absolute URLs. Reject or canonicalize URLs from JWT claims:
from urllib.parse import urlparse
from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse
app = FastAPI()
def is_safe_url(url: str) -> bool:
parsed = urlparse(url)
# Allow only http/https and non-empty host
return parsed.scheme in {"http", "https"} and parsed.netloc
@app.get("/redirect")
def redirect_to(target: str):
if not is_safe_url(target):
raise HTTPException(status_code=400, detail="Invalid redirect URL")
# If target came from a JWT claim, validate thoroughly
return RedirectResponse(url=target)
3) Encode or omit JWT-derived values in custom headers
If you must include a JWT claim in a header, base64-encode it or use a mapping to avoid newline interpretation:
import base64
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/data")
def data(token_sub: str = Header(...)):
encoded = base64.b64encode(token_sub.encode("utf-8")).decode("ascii")
return {"X-Sub-B64": encoded}
4) Centralize header construction and logging
Create a helper for header-safe outputs and ensure logging does not mirror unsanitized values into response headers. For example, when using middleware to log requests, sanitize any values taken from JWT claims:
def make_safe_header(value: str) -> str:
return value.replace("\r", "").replace("\n", "").strip()
By validating JWT-derived inputs, disallowing newline characters in headers, and using safe redirects, you mitigate CRLF injection risks while still leveraging JWT claims in your Fastapi application.