Crlf Injection in Fastapi
How Crlf Injection Manifests in Fastapi
Crlf Injection in Fastapi occurs when untrusted user input containing carriage return (CR) or line feed (LF) characters is incorporated into HTTP headers or other protocol elements without proper sanitization. Fastapi applications are particularly vulnerable because they often use dependency injection and automatic request parsing, which can inadvertently pass raw user data to header construction.
Consider this common Fastapi pattern:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/user")
async def get_user(request: Request):
user_agent = request.headers.get("user-agent")
response = JSONResponse(
content={"message": "Hello!"},
headers={"X-User-Agent": user_agent}
)
return responseAn attacker could send a request with a crafted User-Agent header:
User-Agent: Mozilla/5.0
Set-Cookie: session=malicious; HttpOnly
Content-Type: text/htmlThis would result in the response containing:
HTTP/1.1 200 OK
X-User-Agent: Mozilla/5.0
Set-Cookie: session=malicious; HttpOnly
Content-Type: text/html
{"message": "Hello!"}The attacker has successfully injected new headers into the response, potentially stealing cookies or manipulating client behavior.
Fastapi's dependency injection system can exacerbate this issue. When using Path, Query, or Header parameters, developers might assume Fastapi automatically sanitizes input:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/report")
async def generate_report(report_id: str = Header(...)):
# report_id comes directly from the header
# without sanitization
return JSONResponse(
content={"report": f"Report {report_id} generated"},
headers={"X-Report-ID": report_id}
)Another Fastapi-specific vulnerability arises with response_model and response headers. When using Pydantic models with custom headers:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ReportResponse(BaseModel):
report_id: str
class Config:
@staticmethod
def schema_extra(schema, model):
schema.update({
"headers": {
"X-Report-ID": "{{report_id}}"
}
})
@app.post("/reports", response_model=ReportResponse)
async def create_report(report: ReportRequest):
return ReportResponse(report_id=report.id)If report.id contains CRLF characters, they can be injected into the response headers through the schema_extra configuration.
Fastapi-Specific Detection
Detecting CRLF Injection in Fastapi applications requires both static analysis and dynamic testing. For static analysis, scan your codebase for patterns where user input flows into header construction or response manipulation.
Using middleBrick's CLI tool, you can scan your Fastapi endpoints for CRLF vulnerabilities:
npx middlebrick scan https://your-fastapi-app.com --category "Input Validation"middleBrick's scanner specifically tests for CRLF injection by sending payloads containing %0D%0A sequences and monitoring for header injection in responses. The scanner evaluates 12 security categories including Authentication, BOLA/IDOR, and Input Validation, with findings mapped to OWASP API Top 10.
For manual testing in Fastapi, use curl to test header injection:
curl -v -H "User-Agent: Mozilla/5.0%0D%0ASet-Cookie:%20malicious%3Dtrue%3B%20HttpOnly%0D%0AContent-Type:%20text/html" \
https://your-fastapi-app.com/userWatch for injected headers in the response. If you see Set-Cookie or other headers appear that you didn't program, you have a CRLF vulnerability.
Fastapi's built-in logging can help detect suspicious patterns. Enable detailed logging and monitor for unusual header patterns:
from fastapi import FastAPI
import logging
app = FastAPI()
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
@app.middleware("http")
async def crlf_detection_middleware(request: Request, call_next):
for header, value in request.headers.items():
if '\r' in value or '\n' in value:
logger.warning(f"CRLF characters detected in header {header}: {value}")
response = await call_next(request)
return responsemiddleBrick's continuous monitoring (Pro plan) can automatically scan your Fastapi endpoints on a schedule, alerting you when new CRLF vulnerabilities are detected in production APIs.
Fastapi-Specific Remediation
Remediating CRLF Injection in Fastapi requires input sanitization before header construction. Fastapi provides several approaches to prevent this vulnerability.
The most robust solution is to sanitize input using Python's built-in capabilities:
import re
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
def sanitize_crlf(input_str: str) -> str:
"""Remove CR and LF characters from input"""
if not input_str:
return input_str
# Remove carriage return and line feed characters
sanitized = re.sub(r'[
]', '', input_str)
return sanitized
@app.get("/user")
async def get_user(request: Request):
user_agent = request.headers.get("user-agent", "")
sanitized_agent = sanitize_crlf(user_agent)
response = JSONResponse(
content={"message": "Hello!"},
headers={"X-User-Agent": sanitized_agent}
)
return responseFor header parameters specifically, Fastapi's Header class allows validation:
from fastapi import FastAPI, Header, HTTPException
import re
app = FastAPI()
def validate_no_crlf(value: str):
if '\r' in value or '\n' in value:
raise HTTPException(
status_code=400,
detail="Header contains invalid characters"
)
return value
@app.get("/report")
async def generate_report(
report_id: str = Header(..., regex=r'^[a-zA-Z0-9_-]+$')
):
# The regex ensures only alphanumeric and common safe characters
return JSONResponse(
content={"report": f"Report {report_id} generated"},
headers={"X-Report-ID": report_id}
)For response models with custom headers, validate before serialization:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ReportResponse(BaseModel):
report_id: str
@app.post("/reports", response_model=ReportResponse)
async def create_report(report: ReportRequest):
# Sanitize before returning
sanitized_id = sanitize_crlf(report.id)
return ReportResponse(report_id=sanitized_id)Implement a global middleware to sanitize all headers:
from fastapi import FastAPI, Request, Response
app = FastAPI()
def sanitize_headers(headers: dict) -> dict:
sanitized = {}
for key, value in headers.items():
if isinstance(value, str):
sanitized[key] = sanitize_crlf(value)
else:
sanitized[key] = value
return sanitized
@app.middleware("http")
async def sanitize_middleware(request: Request, call_next):
# Sanitize request headers
sanitized_headers = sanitize_headers(request.headers)
response = await call_next(request)
# Sanitize response headers
response.headers = sanitize_headers(response.headers)
return responseFor production Fastapi applications, combine input validation, sanitization, and monitoring. middleBrick's Pro plan includes continuous scanning that can detect if CRLF vulnerabilities reappear after remediation, ensuring your fixes remain effective over time.