
Безопасное офлайн-воспроизведение CVE-2026-93355 — захват учётной записи LiteLLM через JWT с неподтверждённым адресом электронной почты (используется реальный код LiteLLM)
Автономная одноразовая лабораторная среда, воспроизводящая
CVE-2026-93355: JWT-аутентификация в LiteLLM
определяет личность вызывающего субъекта, прибегая к сопоставлению по email
в качестве запасного варианта — и никогда не проверяет утверждение email_verified. Злоумышленник, который может
заставить свою учётную запись у провайдера идентификации выпустить корректно подписанный токен, содержащий
адрес электронной почты жертвы (неподтверждённый), наследует роль жертвы вплоть до
proxy_admin, а сопоставление молча перезаписывает сохранённую привязку SSO жертвы
на субъект злоумышленника, делая захват постоянным.
| Рекомендация | VulnCheck · OX Security |
| CVE | CVE-2026-93355 |
| Проект | litellm (прокси / JWT-аутентификация) |
| Затронуто | ≤ 1.102.1 согласно рекомендации — до сих пор не исправлено: уязвимый код не изменён в 1.104.0 (последняя стабильная версия) и в main по состоянию на 2026-10-07 |
| CVSS | 8.1 HIGH (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N) |
| Слабость | CWE-1390 — слабая аутентификация |
| Корневая причина | Поиск пользователя по email, которому доверяют без проверки email_verified |
| Обнаружение | Nir Zadok & Moshe Siman Tov Bustan (OX Security) |
Статус исправления (2026-10-07): исправленного релиза пока не существует. В рекомендации указана только
1.102.1, поскольку это была новейшая версия на момент раскрытия; с тех пор LiteLLM выпустил1.103.xи1.104.0, и_get_fuzzy_user_objectв обеих — и вmain— по-прежнему не содержит проверкиemail_verified. Лаборатория фиксирует1.102.1для воспроизводимости;scripts/verify_against_real_source.pyотметит день, когда появится исправление.
Это воспроизводит публично раскрытую уязвимость в образовательных и защитных целях. Всё работает полностью локально и не затрагивает никаких реальных сервисов:
[email protected]
внутри этого процесса и выводит её. Здесь нет реальной учётной записи, реального
ключа, и ничего не покидает вашу машину.Прокси LiteLLM позволяет аутентифицироваться с помощью JWT от настроенного вами провайдера
идентификации. Чтобы сопоставить этот токен с пользователем LiteLLM, он вызывает get_user_object(),
который сначала пробует субъект токена (sso_user_id), а при неудаче прибегает к
_get_fuzzy_user_object() — сопоставлению без учёта регистра по утверждению email.
Кому принадлежит этот email в таблице LiteLLM, тем вы и становитесь. Утверждение токена
email_verified никогда не читается — ни при извлечении утверждений
(JWTHandler.get_user_email), ни при разрешении. Поэтому любой IdP, который выпустит токен для
неподтверждённого email (большинство так и сделает по своей природе — проверка является
задачей проверяющей стороны), позволяет злоумышленнику указать в своём профиле email жертвы,
предъявить токен и унаследовать роль жертвы. Хуже того, при совпадении по email
LiteLLM запускает фоновую задачу, которая перезаписывает sso_user_id жертвы
субъектом злоумышленника — так что каждый последующий вход разрешается напрямую в
учётную запись жертвы без необходимости в email, и смена email не помогает.

Всё тестируемое — это собственный код LiteLLM, импортированный из зафиксированного
пакета litellm==1.102.1. Лаборатория предоставляет только две необходимые вещи: поддельную
таблицу пользователей в памяти и пару ключей мок-IdP. Она не переписывает
уязвимую логику.
inspect.getsource() для установленных
_get_fuzzy_user_object и JWTHandler.get_user_email и проверяем, что резервный
поиск по email существует, переназначение sso_user_id существует, и ни одна из функций
не ссылается на email_verified. → scripts/verify_against_real_source.py_get_fuzzy_user_object;
он сопоставляется со строкой proxy_admin жертвы. → poc/01_account_takeover.pysso_user_id жертвы на subject атакующего; последующий вход вообще без email
напрямую сопоставляется с администратором, и «исправление дублирующегося email» это не отзывает.
→ poc/02_persistent_rebind.pyemail_verified перед резервным поиском по email блокирует
атаку, сохраняя работоспособность подтверждённых SSO-входов. → poc/03_patched_path.py./run.sh
run.sh создаёт временный .venv, устанавливает litellm[proxy]==1.102.1, запускает
проверку эталонного поведения, а затем все три 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 на границе доверия, прежде чем какой-либо токен
дойдёт до сопоставления личности; отклоняйте или игнорируйте неподтверждённые email.user_id_jwt_field),
а не к изменяемому email, и не позволяйте резервному варианту с email незаметно перепривязывать её.proxy_admin,
привязанная к email, — это постоянный примитив захвата аккаунта.