SSRF (Server-Side Request Forgery) acontece quando uma aplicação busca um recurso a partir de uma URL controlada pelo usuário sem validar direito o destino. Em vez de atacar o servidor de frente, o atacante faz o servidor atacar por ele. Como a requisição parte de dentro da infraestrutura, ela costuma atravessar firewalls, alcançar serviços internos que não deveriam estar acessíveis e herdar a confiança que a rede interna deposita em si mesma.
Em ambientes de nuvem o SSRF fica especialmente perigoso. É a porta de entrada clássica para o roubo de credenciais temporárias pelo endpoint de metadados.
Onde o SSRF nasce
Qualquer funcionalidade que receba uma URL e a busque no backend é candidata:
- “Importe sua foto de perfil a partir de uma URL”
- Webhooks e integrações (“envie eventos para esta URL”)
- Geradores de PDF/screenshot que renderizam uma página
- Validadores de URL, link previews, proxies de imagem
- Parsers de XML com entidades externas (XXE que vira SSRF)
# Endpoint vulnerável: busca a URL que o usuário mandar, sem restrição
@app.post("/importar")
def importar():
url = request.json["url"]
resp = requests.get(url, timeout=5) # <- SSRF
return resp.content
O alvo mais valioso: metadados de nuvem
Provedores de nuvem expõem um endpoint de metadados em um IP link-local acessível apenas de dentro da instância. Em muitos ambientes ele entrega credenciais temporárias da role associada à máquina:
http://169.254.169.254/latest/meta-data/iam/security-credentials/
Se o servidor faz a requisição que o atacante dita, dá para pedir esse endereço e receber de volta AccessKeyId, SecretAccessKey e Token, credenciais válidas para falar com a API da nuvem como se fosse a própria aplicação. É a escalada que transforma um “busque esta URL” no comprometimento da conta inteira.
Mitigação específica: exija o IMDSv2 (fluxo baseado em token, com
PUTmais cabeçalho e TTL de hop = 1). Ele quebra a maioria dos SSRFs ingênuos, que só conseguem fazer um GET simples. Ainda assim, trate isso como uma camada, e não como a solução única.
Outros alvos internos comuns: bancos de dados e caches sem autenticação (http://127.0.0.1:6379 em Redis), painéis administrativos internos, APIs de orquestração (Kubernetes, Consul) e o velho localhost.
SSRF cego (blind)
Nem sempre a resposta volta para o atacante. No SSRF cego, a aplicação faz a requisição mas não devolve o corpo. Mesmo assim dá para explorar:
- Exfiltração por canal lateral: diferenças no tempo de resposta ou no código de status revelam se uma porta interna está aberta, o que serve para varrer a rede de dentro.
- Interações fora de banda: você aponta para um domínio que controla (servidor DNS/HTTP do atacante) e observa a chamada chegar. Isso confirma o SSRF e pode vazar dados pelo subdomínio.
Por que filtros ingênuos falham — os bypasses
A reação instintiva é bloquear localhost e 169.254.169.254. Isso quase nunca basta. Veja os bypasses clássicos (você gera todas estas variações de um IP automaticamente no Helper SSRF):
Representações alternativas do IP:
http://2130706433/ # 127.0.0.1 em decimal
http://0x7f000001/ # 127.0.0.1 em hexadecimal
http://0177.0.0.1/ # octal
http://127.1/ # forma curta
http://[::1]/ # IPv6 loopback
http://[::ffff:169.254.169.254]/ # IPv4 mapeado em IPv6
Truques de URL e parsing:
http://169.254.169.254.nip.io/ # DNS que resolve para o IP interno
http://atacante.com@169.254.169.254/ # confunde validadores que olham só o "host" textual
http://metadata/ # caracteres unicode/encoding
Redirect: o atacante aponta para http://site-do-atacante.com/r, que responde 302 Location: http://169.254.169.254/.... Se a aplicação segue redirects e só validou a URL inicial, o filtro já era.
DNS rebinding: o domínio do atacante resolve para um IP público na primeira consulta, passando na validação, e para 169.254.169.254 na segunda, no momento do fetch real. A vulnerabilidade mora na janela entre validar e buscar. Por isso validar a string da URL não basta: o que conta é o IP de destino no instante da conexão.
A arquitetura de defesa correta
SSRF não se resolve com uma regex. A defesa é em camadas, do código à rede.
1. Allowlist, nunca blocklist
Defina de forma explícita os destinos permitidos. Se a funcionalidade só precisa falar com api.parceiro.com, libere apenas esse host. Todo o resto fica negado por padrão.
ALLOWED_HOSTS = {"api.parceiro.com", "cdn.parceiro.com"}
def fetch_seguro(url):
parts = urlparse(url)
if parts.scheme not in ("https",): # só https
raise ValueError("esquema não permitido")
if parts.hostname not in ALLOWED_HOSTS: # allowlist de host
raise ValueError("host não permitido")
# Resolve e valida o IP ANTES de conectar (ver passo 2)
...
2. Resolva o DNS e valide o IP — e conecte nesse IP
Para derrotar DNS rebinding e redirects, resolva o nome, rejeite IPs privados e reservados e force a conexão a usar exatamente o IP que você validou, sem nova resolução:
import ipaddress, socket
BLOCKED_NETS = [
ipaddress.ip_network(n) for n in [
"127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16",
"169.254.0.0/16", "::1/128", "fc00::/7", "fe80::/10",
]
]
def ip_permitido(host):
for fam, _, _, _, sockaddr in socket.getaddrinfo(host, None):
ip = ipaddress.ip_address(sockaddr[0])
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
return False
if any(ip in net for net in BLOCKED_NETS):
return False
return True
Valide todos os IPs retornados, já que um host pode ter vários registros A/AAAA, e revalide depois de cada redirect. Mais simples ainda: desabilite o follow de redirects no cliente HTTP.
3. Segmentação de rede (a camada que salva)
Trate o código como falível e isole por rede. A carga de trabalho que faz fetch externo não deveria ter rota para o endpoint de metadados, para a sub-rede de bancos de dados ou para painéis internos. Egress filtering e security groups restritivos transformam um SSRF bem-sucedido em um beco sem saída.
4. Endurecimento específico de nuvem
- IMDSv2 obrigatório com
hop limit = 1. - Roles com privilégio mínimo: se as credenciais vazarem, o estrago fica contido.
- Bloquear, por rota de rede, o acesso da aplicação ao
169.254.169.254quando ela não precisa de metadados.
Como testamos SSRF
- Catalogar toda entrada que vira uma requisição no servidor (URLs, webhooks, parsers, importadores).
- Apontar para um listener fora de banda controlado e confirmar a chamada chegando (prova de SSRF, inclusive cego).
- Testar a bateria de bypasses: IP decimal/hex/octal, IPv6,
@,nip.io, redirect 302, DNS rebinding. - Tentar alcançar
169.254.169.254,127.0.0.1em portas internas e serviços de orquestração. - Medir o impacto real: leitura de metadados, varredura interna, acesso a serviços sem autenticação.
Checklist de mitigação
- Allowlist de host e esquema (
https) — negar por padrão. - Resolver DNS, rejeitar IPs privados/reservados e conectar no IP validado.
- Não seguir redirects (ou revalidar o destino a cada salto).
- Segmentação de rede / egress filtering isolando a carga do metadado e da rede interna.
- IMDSv2 obrigatório, roles com privilégio mínimo.
- Desabilitar esquemas perigosos (
file://,gopher://,dict://) no cliente HTTP. - Timeouts curtos e resposta genérica (não devolver corpo/erro que sirva de oráculo).
O SSRF é a vulnerabilidade que melhor mostra por que “validar a entrada” e “validar o destino” são problemas diferentes. Uma defesa que funciona não acredita na string da URL. Ela controla para qual IP, em qual rede, o servidor tem permissão de conectar.


