CVE-2026-33634: LiteLLM api_base SSRF — credential-theft PoC labCVE-2026-33634: SSRF via api_base no LiteLLM — lab de roubo de credenciais
A dockerized lab that chains a LiteLLM-style gateway's unvalidated api_base parameter (SSRF) with a trojanized telemetry dependency, walking an attacker from a single request to full provider-key and cloud-credential exfiltration.Um laboratório dockerizado que encadeia o parâmetro api_base não validado de um gateway estilo LiteLLM (SSRF) com uma dependência de telemetria trojanizada, levando o atacante de uma única requisição à exfiltração completa de chaves de provedores e credenciais de nuvem.
Proof of concept on GitHubProva de conceito no GitHubAuthorization: this is a deliberately vulnerable, self-contained lab built for education and authorized security research. Every service binds to
127.0.0.1and all traffic stays inside an isolated Docker network. Run it only on a disposable host you own — never on a shared or cloud VM, and never against infrastructure you aren't explicitly authorized to test. Misuse can be a crime.
Summary
CVE-2026-33634 is a critical (CVSS 9.4) server-side request forgery in a
LiteLLM-style LLM gateway. The proxy accepts the provider base URL
(api_base) straight from the caller, with no allowlist, then makes the outbound
request itself — attaching a legitimate provider credential to it and returning
the upstream body to the caller.
This repository reproduces the March 2026 incident as a full attacker-and-victim lab. It chains two failures that, together, turn one unauthenticated request into total credential compromise:
- an SSRF in the gateway's
api_basehandling, and - a trojanized transitive dependency (
litellm-telemetry-helper) that exfiltrates environment secrets at import time.
Everything is wired together with Docker Compose so the whole chain — gateway, attacker collector, and the internal-only services the SSRF reaches — runs on one machine.
How the chain works
The SSRF sink
The gateway treats api_base as a trusted routing hint. When a request includes
it, the proxy:
- issues an outbound HTTP request to that attacker-chosen destination,
- appends the real provider's credential in the
Authorizationheader, and - returns the upstream response body verbatim.
With no allowlist and no egress restrictions, "the provider base URL" becomes "any host and path the gateway can reach" — internal services, the cloud metadata endpoint, or attacker infrastructure that simply records the header it's handed.
The single request that demonstrates the sink:
curl -s http://localhost:4000/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"gpt-4o","api_base":"http://internal.lab:9000/admin/keys","messages":[]}'The supply-chain beacon
The second act is a dependency the gateway imports. In the lab it's pinned loosely
(>=0.9.6), so a malicious 0.9.7 resolves in. On import it spawns a
background thread that ships environment variables — OPENAI_API_KEY,
ANTHROPIC_API_KEY, cloud credentials — to the attacker's collector, silently,
without breaking the application.
The lab is explicit about where it simplifies the real incident: a direct local import stands in for full PyPI resolution, and the SSRF path is arbitrary within the lab network rather than host-controlled. The impact — credential leakage plus an internal pivot — is faithful.
Impact
Chained, the two flaws hand an attacker the victim's entire credential portfolio:
- Provider-key disclosure — point
api_baseat attacker infrastructure and the gateway leaks each provider's key in theAuthorizationheader it attaches. - Internal reconnaissance & compromise — reach non-routable services such as
/admin/keysthat were never meant to face the caller. - Cloud credential theft — query the instance metadata service
(
169.254.169.254) for temporary STS credentials. - Network reconnaissance — port-scan internal infrastructure through the gateway as a proxy.
- Environment exfiltration — every secret in the gateway's environment, beaconed out at import time by the trojanized dependency.
Preconditions
| Condition | Why it matters |
|---|---|
Gateway accepts api_base from an untrusted caller | The SSRF entry point |
No allowlist / validation of api_base destinations | Lets the caller aim the request at any reachable host |
| No egress firewall or IP-range restriction on outbound | Permits reaching internal, link-local, and metadata IPs |
| Gateway imports the malicious dependency (transitively) | Arms the import-time exfiltration beacon |
The lab
Services
| Service | Port | Role |
|---|---|---|
gateway | 4000 | LiteLLM-style proxy — the SSRF sink and malicious-dep host |
collector | 8080 | Attacker endpoint (/beacon, /collect, /oob, /loot) |
internal.lab | 9000 | Secret vault (/admin/keys) — non-routable, SSRF-only |
imds.lab | 80 | Cloud metadata mock — non-routable, SSRF-only |
provider-mock.lab | 9100 | Legitimate upstream provider |
Layout
CVE-2026-33634/
├── gateway/ # SSRF sink + malicious-dep import
├── malicious-dep/ # trojanized package; payload runs at import
├── collector/ # attacker collection endpoints
├── internal-service/ # /admin/keys — SSRF target
├── imds/ # metadata service mock — SSRF target
├── provider-mock/ # normal upstream
├── exploit/exploit.py # multi-phase async exploit
├── docker-compose.yml
└── SECURITY-NOTES.mdExploitation
The provided exploit.py runs the attack in phases, each mapping to one link in
the chain:
| Phase | What it does |
|---|---|
recon | Fingerprint the gateway, enumerate providers, detect the api_base sink |
ssrf | Confirm blind SSRF out-of-band via a unique callback token |
scan | Port-scan the internal network through the gateway |
internal | SSRF to internal.lab/admin/keys for the credential vault |
cloud | SSRF to the IMDS mock for temporary STS credentials |
keyleak | Redirect api_base to the attacker; capture leaked provider keys |
supplychain | Read the environment secrets beaconed by the malicious dependency |
report | Consolidate everything into loot.json |
Running it
Prerequisites: Docker and Docker Compose v2; Python 3.9+ with httpx.
1. Bring up the lab:
docker compose up -d --build2. Install the exploit's dependencies and run the full chain:
python3 -m pip install -r exploit/requirements.txt
python3 exploit/exploit.pyRun a single phase to focus on one technique:
python3 exploit/exploit.py --only recon,ssrf
python3 exploit/exploit.py --only internal
python3 exploit/exploit.py --only cloud
python3 exploit/exploit.py --only keyleak
python3 exploit/exploit.py --only supplychain3. Inspect the collected loot — environment secrets, internal credentials,
cloud STS tokens, and provider keys, consolidated by the report phase:
curl -s http://localhost:8080/loot | python3 -m json.toolMitigations
The lab's SECURITY-NOTES.md spells out the fixes, and they map straight back to
the two failures:
- Allowlist
api_baseto known provider hosts, and block private, loopback, and link-local ranges (including169.254.169.254) on the gateway's egress. - Pin dependencies exactly, with hashes, rather than
>=ranges, and review transitive packages — an install or import hook executes code. - Keep secrets in a manager, not environment variables, so an import-time beacon has nothing to read.
References
Autorização: este é um laboratório deliberadamente vulnerável, feito para educação e pesquisa de segurança autorizada. Todos os serviços escutam em
127.0.0.1e todo o tráfego permanece dentro de uma rede Docker isolada. Rode apenas em uma máquina descartável que seja sua — nunca em uma VM compartilhada ou de nuvem, e nunca contra infraestrutura que você não esteja explicitamente autorizado a testar. O uso indevido pode ser crime.
Resumo
A CVE-2026-33634 é um server-side request forgery crítico (CVSS 9.4) em um
gateway de LLM estilo LiteLLM. O proxy aceita a URL base do provedor
(api_base) diretamente de quem chama, sem allowlist, e então faz a requisição de
saída ele mesmo — anexando a ela uma credencial legítima do provedor e devolvendo
o corpo da resposta upstream para o chamador.
Este repositório reproduz o incidente de março de 2026 como um laboratório completo de atacante e vítima. Ele encadeia duas falhas que, juntas, transformam uma única requisição não autenticada em comprometimento total de credenciais:
- um SSRF no tratamento do
api_basepelo gateway, e - uma dependência transitiva trojanizada (
litellm-telemetry-helper) que exfiltra segredos do ambiente no momento do import.
Tudo é conectado com Docker Compose, de modo que a cadeia inteira — gateway, collector do atacante e os serviços internos que o SSRF alcança — roda em uma só máquina.
Como a cadeia funciona
O ponto de SSRF
O gateway trata api_base como uma dica de roteamento confiável. Quando uma
requisição a inclui, o proxy:
- dispara uma requisição HTTP de saída para aquele destino escolhido pelo atacante,
- anexa a credencial real do provedor no cabeçalho
Authorization, e - devolve o corpo da resposta upstream tal e qual.
Sem allowlist e sem restrições de egresso, "a URL base do provedor" vira "qualquer host e caminho que o gateway consiga alcançar" — serviços internos, o endpoint de metadados da nuvem, ou infraestrutura do atacante que apenas registra o cabeçalho que recebe.
A única requisição que demonstra o ponto vulnerável:
curl -s http://localhost:4000/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"gpt-4o","api_base":"http://internal.lab:9000/admin/keys","messages":[]}'O beacon de supply chain
O segundo ato é uma dependência que o gateway importa. No laboratório ela está
fixada de forma frouxa (>=0.9.6), então uma versão maliciosa 0.9.7 é
resolvida. No import, ela cria uma thread em segundo plano que envia variáveis
de ambiente — OPENAI_API_KEY, ANTHROPIC_API_KEY, credenciais de nuvem — para o
collector do atacante, silenciosamente, sem quebrar a aplicação.
O laboratório é explícito sobre onde simplifica o incidente real: um import local direto faz o papel da resolução completa via PyPI, e o caminho do SSRF é arbitrário dentro da rede do laboratório, e não controlado por host. O impacto — vazamento de credenciais mais um pivô interno — é fiel.
Impacto
Encadeadas, as duas falhas entregam ao atacante todo o portfólio de credenciais da vítima:
- Divulgação de chaves de provedores — aponte
api_basepara a infraestrutura do atacante e o gateway vaza a chave de cada provedor no cabeçalhoAuthorizationque anexa. - Reconhecimento e comprometimento interno — alcance serviços não roteáveis
como
/admin/keys, que nunca deveriam ficar expostos a quem chama. - Roubo de credenciais de nuvem — consulte o serviço de metadados da instância
(
169.254.169.254) em busca de credenciais STS temporárias. - Reconhecimento de rede — faça port-scan da infraestrutura interna usando o gateway como proxy.
- Exfiltração do ambiente — todo segredo no ambiente do gateway, enviado no momento do import pela dependência trojanizada.
Pré-condições
| Condição | Por que importa |
|---|---|
Gateway aceita api_base de um chamador não confiável | O ponto de entrada do SSRF |
Sem allowlist / validação dos destinos de api_base | Deixa o chamador mirar a requisição em qualquer host alcançável |
| Sem firewall de egresso ou restrição de faixa de IP na saída | Permite alcançar IPs internos, link-local e de metadados |
| Gateway importa a dependência maliciosa (transitivamente) | Arma o beacon de exfiltração no momento do import |
O laboratório
Serviços
| Serviço | Porta | Papel |
|---|---|---|
gateway | 4000 | Proxy estilo LiteLLM — o ponto de SSRF e host da dep maliciosa |
collector | 8080 | Endpoint do atacante (/beacon, /collect, /oob, /loot) |
internal.lab | 9000 | Cofre de segredos (/admin/keys) — não roteável, só via SSRF |
imds.lab | 80 | Mock de metadados da nuvem — não roteável, só via SSRF |
provider-mock.lab | 9100 | Provedor upstream legítimo |
Estrutura
CVE-2026-33634/
├── gateway/ # ponto de SSRF + import da dep maliciosa
├── malicious-dep/ # pacote trojanizado; payload roda no import
├── collector/ # endpoints de coleta do atacante
├── internal-service/ # /admin/keys — alvo do SSRF
├── imds/ # mock do serviço de metadados — alvo do SSRF
├── provider-mock/ # upstream normal
├── exploit/exploit.py # exploit assíncrono multi-fase
├── docker-compose.yml
└── SECURITY-NOTES.mdExploração
O exploit.py fornecido roda o ataque em fases, cada uma mapeando para um elo da
cadeia:
| Fase | O que faz |
|---|---|
recon | Faz fingerprint do gateway, enumera provedores, detecta o ponto api_base |
ssrf | Confirma SSRF cego out-of-band via um token de callback único |
scan | Faz port-scan da rede interna através do gateway |
internal | SSRF para internal.lab/admin/keys em busca do cofre de credenciais |
cloud | SSRF para o mock de IMDS em busca de credenciais STS temporárias |
keyleak | Redireciona api_base para o atacante; captura chaves de provedor vazadas |
supplychain | Lê os segredos de ambiente enviados pela dependência maliciosa |
report | Consolida tudo em loot.json |
Executando
Pré-requisitos: Docker e Docker Compose v2; Python 3.9+ com httpx.
1. Suba o laboratório:
docker compose up -d --build2. Instale as dependências do exploit e rode a cadeia completa:
python3 -m pip install -r exploit/requirements.txt
python3 exploit/exploit.pyRode uma fase isolada para focar em uma técnica:
python3 exploit/exploit.py --only recon,ssrf
python3 exploit/exploit.py --only internal
python3 exploit/exploit.py --only cloud
python3 exploit/exploit.py --only keyleak
python3 exploit/exploit.py --only supplychain3. Inspecione o loot coletado — segredos de ambiente, credenciais internas,
tokens STS de nuvem e chaves de provedores, consolidados pela fase report:
curl -s http://localhost:8080/loot | python3 -m json.toolMitigações
O SECURITY-NOTES.md do laboratório detalha as correções, e elas mapeiam direto
para as duas falhas:
- Allowlist para
api_basecom os hosts de provedor conhecidos, e bloqueie faixas privadas, de loopback e link-local (incluindo169.254.169.254) no egresso do gateway. - Fixe dependências de forma exata, com hashes, em vez de faixas
>=, e revise pacotes transitivos — um hook de install ou import executa código. - Mantenha segredos em um gerenciador, não em variáveis de ambiente, para que um beacon no momento do import não tenha nada para ler.