Overview
Improper handling of JSON payloads in FastAPI can enable CSRF-like attacks when cookie-based authentication is used on endpoints that accept JSON payloads. In real-world terms, an attacker could lure a logged-in user to visit a malicious page which issues a request to the victim’s FastAPI application with a crafted JSON body. If the server reads the payload as JSON irrespective of the content-type and relies on cookies for authentication, the action could be performed with the user’s privileges without an explicit, user-initiated action. This class of vulnerability aligns with CWE-352 (CSRF) and was evidenced in CVE-2021-32677, where FastAPI versions below 0.65.2 parsed request bodies as JSON even when the Content-Type header was not application/json or a JSON-compatible type. The root issue is improper input handling combined with cookie-based auth and permissive content parsing. The fix is to ensure JSON is parsed only when the request explicitly declares a JSON content-type, upgrade to a patched version, or enforce middleware that rejects non-JSON payloads for endpoints using cookies for auth. It is also best practice to move to header-based authentication (e.g., OAuth2/JWT) and to implement CSRF protections for state-changing endpoints. This guidance is anchored to CVE-2021-32677 and demonstrates how the vulnerability manifests in FastAPI-based services used for inventory or similar stateful operations.
Affected Versions
< 0.65.2
Code Fix Example
FastAPI API Security Remediation
Vulnerable pattern (demonstrative, side-by-side with fix in the same snippet):
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
app_vuln = FastAPI()
@app_vuln.post("/inventory/update")
async def update_inventory(request: Request):
# Vulnerable: relies on cookie-based auth and reads JSON payload without enforcing content-type
cookie = request.cookies.get("session")
if not cookie:
raise HTTPException(401, "Unauthorized")
# In older FastAPI versions (<0.65.2), this could parse JSON even if Content-Type isn't application/json
payload = await request.json()
item_id = payload.get("item_id")
# perform inventory update using item_id
return {"status": "updated", "item_id": item_id}
# Fixed version (upgrade or content-type enforcement):
from fastapi import Depends
app_fixed = FastAPI()
def enforce_json_content_type(request: Request):
# Enforce JSON content-type for endpoints that expect JSON payloads
ctype = request.headers.get("content-type", "").split(";")[0].strip()
if ctype not in ("application/json", "application/geo+json"):
raise HTTPException(status_code=415, detail="Invalid Content-Type for JSON payload")
@app_fixed.post("/inventory/update")
async def update_inventory_fixed(request: Request, _=Depends(enforce_json_content_type)):
cookie = request.cookies.get("session")
if not cookie:
raise HTTPException(401, "Unauthorized")
payload = await request.json()
item_id = payload.get("item_id")
return {"status": "updated", "item_id": item_id}
# Alternate mitigation: upgrade to FastAPI 0.65.2+ and/or switch to header-based auth (e.g., OAuth2/JWT) for state-changing endpoints.