
CVE-2026-93355 の無害なオフライン再現 — LiteLLM の未検証メール JWT アカウント乗っ取り(LiteLLM の実際のコードを実行)
CVE-2026-93355 を再現する、自己完結型の使い捨てラボです:LiteLLM の JWT 認証は、呼び出し元の身元を メール一致 へのフォールバックによって解決し、email_verified クレームを一切チェックしません。攻撃者が自身の ID プロバイダアカウントから、被害者のメールアドレス(未検証)を含む正当に署名されたトークンを発行させることができれば、被害者のロールを継承し、proxy_admin にまで昇格できます。さらに、この一致は被害者の保存済み SSO バインディングを攻撃者のサブジェクトへとサイレントに書き換えるため、乗っ取りが永続化します。
| アドバイザリ | VulnCheck · OX Security |
| CVE | CVE-2026-93355 |
| プロジェクト | litellm(プロキシ / JWT 認証) |
| 影響範囲 | アドバイザリによれば 1.102.1 以下 — 未修正のまま:脆弱なコードは 1.104.0(最新安定版)および 2026-10-07 時点の main でも変更されていない |
| CVSS | 8.1 HIGH (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N) |
| 弱点 | CWE-1390 — 弱い認証 |
| 根本原因 | email_verified を検証せずにメールベースのユーザー検索を信頼している |
| 発見者 | Nir Zadok & Moshe Siman Tov Bustan (OX Security) |
修正状況 (2026-10-07): 修正済みリリースはまだ存在しません。アドバイザリが
1.102.1を挙げているのは、開示時点での最新バージョンだったからにすぎません。LiteLLM は その後1.103.xと1.104.0をリリースしていますが、その両方 — そしてmain上でも —_get_fuzzy_user_objectには依然としてemail_verifiedチェックがありません。このラボは 再現性のために1.102.1を固定しています。scripts/verify_against_real_source.pyは 修正が入った日を検出します。
これは教育および防御目的のために、公開開示された脆弱性を再現するものです。完全にローカルで動作し、実在のサービスには一切触れません:
[email protected] 管理者行を解決して表示するだけです。実在のアカウントも実在の鍵もなく、何もあなたのマシンから外に出ません。LiteLLM のプロキシでは、設定された ID プロバイダからの JWT で認証できます。そのトークンを LiteLLM ユーザーにマッピングするために get_user_object() を呼び出し、これはトークンのサブジェクト(sso_user_id)を試し、見つからない場合は _get_fuzzy_user_object() にフォールバックします — これは email クレームの大文字小文字を区別しない一致です。LiteLLM のテーブルでそのメールを所有している者が、あなたの身元になります。トークンの email_verified クレームは一切読まれません — クレーム抽出時(JWTHandler.get_user_email)にも、解決時にも。したがって、未検証のメールに対してトークンを発行する IdP(大半は設計上そうします — 検証は relying party の責務です)であれば、攻撃者は自身のプロフィールメールを被害者のものに設定し、トークンを提示して被害者のロールを継承できます。さらに悪いことに、メール一致時には LiteLLM がバックグラウンドタスクを起動し、被害者の sso_user_id を攻撃者のサブジェクトで上書きします — そのため以降のすべてのログインはメールを必要とせずに被害者のアカウントへ直接解決され、メールを変更しても役に立ちません。

テスト対象のすべてはLiteLLM 自身のコードであり、固定された litellm==1.102.1 パッケージからインポートされます。ラボが提供するのは、必要不可欠な 2 つだけです:インメモリの偽ユーザーテーブルと、モック IdP の鍵ペアです。脆弱なロジックを再実装することはありません。
_get_fuzzy_user_object と JWTHandler.get_user_email に対して inspect.getsource() を実行し、メールフォールバックが存在すること、sso_user_id の再バインドが存在すること、そしてどちらの関数も email_verified を参照していないことを検証します。 → scripts/verify_against_real_source.py_get_fuzzy_user_object に通します。その結果、被害者の proxy_admin 行に解決されます。 → poc/01_account_takeover.pysso_user_id を攻撃者のサブジェクトに再バインドします。その後、メールアドレスを一切含まないログインが直接管理者に解決され、「重複メールを修正する」ことではそれが取り消されません。 → poc/02_persistent_rebind.pyemail_verified を強制することで、検証済みの SSO ログインを機能させたまま攻撃をブロックします。 → poc/03_patched_path.py./run.sh
run.sh は使い捨ての .venv を構築し、litellm[proxy]==1.102.1 をインストールして、グラウンドトゥルースチェックを実行した後、3 つの PoC をすべて実行します。初回実行時は LiteLLM をダウンロードするため数分かかります。クリーンアップするには .venv を削除してください。既存のインタプリタを使用するには:PYTHON=python3.12 ./run.sh。
単一の PoC を直接実行するには(venv 内で):
.venv/bin/python poc/01_account_takeover.py
cve-2026-93355-lab/
├── mock_idp.py # ephemeral RS256 OIDC IdP (real signatures)
├── fake_litellm_db.py # in-memory stand-in for the Prisma user table
├── harness.py # configures & drives LiteLLM's real objects
├── poc/
│ ├── 01_account_takeover.py # unverified email -> victim's proxy_admin
│ ├── 02_persistent_rebind.py # sso_user_id overwrite -> persistent access
│ └── 03_patched_path.py # the email_verified guard that closes it
├── scripts/
│ └── verify_against_real_source.py # proves the PoCs run the real vulnerable code
├── assets/attack-flow.svg|.png # the diagram above
├── blog-cve-2026-93355.md # the guard0 write-up
└── run.sh
email_verified: true を強制し、未検証のメールを拒否または無視してください。user_id_jwt_field)にバインドし、メールへのフォールバックによって暗黙的に再バインドされないようにしてください。proxy_admin 行は、常時有効なアカウント乗っ取りのプリミティブとなります。