joaovicdev
exploitexploitCVE-2026-33634

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 GitHub

Authorization: this is a deliberately vulnerable, self-contained lab built for education and authorized security research. Every service binds to 127.0.0.1 and 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:

  1. an SSRF in the gateway's api_base handling, and
  2. 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:

  1. issues an outbound HTTP request to that attacker-chosen destination,
  2. appends the real provider's credential in the Authorization header, and
  3. 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:

manual SSRF — reach an internal-only service through the gateway
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_base at attacker infrastructure and the gateway leaks each provider's key in the Authorization header it attaches.
  • Internal reconnaissance & compromise — reach non-routable services such as /admin/keys that 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

ConditionWhy it matters
Gateway accepts api_base from an untrusted callerThe SSRF entry point
No allowlist / validation of api_base destinationsLets the caller aim the request at any reachable host
No egress firewall or IP-range restriction on outboundPermits reaching internal, link-local, and metadata IPs
Gateway imports the malicious dependency (transitively)Arms the import-time exfiltration beacon

The lab

Services

ServicePortRole
gateway4000LiteLLM-style proxy — the SSRF sink and malicious-dep host
collector8080Attacker endpoint (/beacon, /collect, /oob, /loot)
internal.lab9000Secret vault (/admin/keys) — non-routable, SSRF-only
imds.lab80Cloud metadata mock — non-routable, SSRF-only
provider-mock.lab9100Legitimate upstream provider

Layout

repository 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.md

Exploitation

The provided exploit.py runs the attack in phases, each mapping to one link in the chain:

PhaseWhat it does
reconFingerprint the gateway, enumerate providers, detect the api_base sink
ssrfConfirm blind SSRF out-of-band via a unique callback token
scanPort-scan the internal network through the gateway
internalSSRF to internal.lab/admin/keys for the credential vault
cloudSSRF to the IMDS mock for temporary STS credentials
keyleakRedirect api_base to the attacker; capture leaked provider keys
supplychainRead the environment secrets beaconed by the malicious dependency
reportConsolidate everything into loot.json

Running it

Prerequisites: Docker and Docker Compose v2; Python 3.9+ with httpx.

1. Bring up the lab:

terminal 1
docker compose up -d --build

2. Install the exploit's dependencies and run the full chain:

terminal 2
python3 -m pip install -r exploit/requirements.txt
python3 exploit/exploit.py

Run 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 supplychain

3. 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.tool

Mitigations

The lab's SECURITY-NOTES.md spells out the fixes, and they map straight back to the two failures:

  • Allowlist api_base to known provider hosts, and block private, loopback, and link-local ranges (including 169.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.1 e 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:

  1. um SSRF no tratamento do api_base pelo gateway, e
  2. 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:

  1. dispara uma requisição HTTP de saída para aquele destino escolhido pelo atacante,
  2. anexa a credencial real do provedor no cabeçalho Authorization, e
  3. 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:

SSRF manual — alcança um serviço interno através do gateway
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_base para a infraestrutura do atacante e o gateway vaza a chave de cada provedor no cabeçalho Authorization que 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çãoPor que importa
Gateway aceita api_base de um chamador não confiávelO ponto de entrada do SSRF
Sem allowlist / validação dos destinos de api_baseDeixa o chamador mirar a requisição em qualquer host alcançável
Sem firewall de egresso ou restrição de faixa de IP na saídaPermite 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çoPortaPapel
gateway4000Proxy estilo LiteLLM — o ponto de SSRF e host da dep maliciosa
collector8080Endpoint do atacante (/beacon, /collect, /oob, /loot)
internal.lab9000Cofre de segredos (/admin/keys) — não roteável, só via SSRF
imds.lab80Mock de metadados da nuvem — não roteável, só via SSRF
provider-mock.lab9100Provedor upstream legítimo

Estrutura

estrutura do repositório
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.md

Exploração

O exploit.py fornecido roda o ataque em fases, cada uma mapeando para um elo da cadeia:

FaseO que faz
reconFaz fingerprint do gateway, enumera provedores, detecta o ponto api_base
ssrfConfirma SSRF cego out-of-band via um token de callback único
scanFaz port-scan da rede interna através do gateway
internalSSRF para internal.lab/admin/keys em busca do cofre de credenciais
cloudSSRF para o mock de IMDS em busca de credenciais STS temporárias
keyleakRedireciona api_base para o atacante; captura chaves de provedor vazadas
supplychainLê os segredos de ambiente enviados pela dependência maliciosa
reportConsolida tudo em loot.json

Executando

Pré-requisitos: Docker e Docker Compose v2; Python 3.9+ com httpx.

1. Suba o laboratório:

terminal 1
docker compose up -d --build

2. Instale as dependências do exploit e rode a cadeia completa:

terminal 2
python3 -m pip install -r exploit/requirements.txt
python3 exploit/exploit.py

Rode 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 supplychain

3. 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.tool

Mitigações

O SECURITY-NOTES.md do laboratório detalha as correções, e elas mapeiam direto para as duas falhas:

  • Allowlist para api_base com os hosts de provedor conhecidos, e bloqueie faixas privadas, de loopback e link-local (incluindo 169.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.

Referências