O SSRF (Server-Side Request Forgery) representa uma ameaça crítica em aplicações web que realizam chamadas HTTP dinâmicas com base em entradas externas. Em ambientes Python, esse risco surge frequentemente ao usar bibliotecas como requests, httpx ou urllib sem validação adequada de destinos — permitindo que atacantes forcem o servider a acessar recursos internos, como APIs locais, metadados de nuvem (ex.: AWS IMDS), bancos de dados ou serviços de gerenciamento.
Mceanismos de Exposição Comuns
Os cenários mais suscetíveis incluem:
- Endpoint que aceita um parâmetro
target_urle executarequests.get(target_url)diretamente; - Integrações com serviços de conversão de URL (ex.: captura de thumbnail, pré-renderização);
- Webhooks configuráveis por usuários com URLs arbitrárias;
- Uso de funções como
socket.gethostbyname()ouurllib.parse.urlparse()sem sanitização posterior.
Estratégias Técnicas de Mitigação
1. Validação Estrita de Domínios com Resolução DNS Controlada
Evite depender apenas de expressões regulares para extrair hosts — elas falham com URLs malformadas, Unicode homógrafos ou esquemas não convencionais. Prefira bibliotecas especializadas como urllib3.util.parse_url ou yarl, combinadas com resolução explícita via socket.getaddrinfo para identificar IPs reais antes da requisição:
import socket
from urllib3.util import parse_url
def validate_target_host(url: str, allowed_hosts: set[str]) -> bool:
try:
parsed = parse_url(url)
if not parsed.host or not parsed.scheme or parsed.scheme.lower() not in {"http", "https"}:
return False
# Resolve todas as interfaces IPv4/IPv6 do host
addr_info = socket.getaddrinfo(
parsed.host, None, socket.AF_UNSPEC, socket.SOCK_STREAM
)
ip_addresses = {addr[4][0] for addr in addr_info}
# Verifica se algum IP resolvido pertence a redes proibidas
for ip in ip_addresses:
if is_private_network(ip):
return False
# Valida domínio contra lista branca (sem wildcard ambíguo)
return parsed.host.lower() in allowed_hosts
except (socket.gaierror, ValueError, OSError):
return False
def is_private_network(ip: str) -> bool:
try:
addr = ipaddress.ip_address(ip)
return (
addr.is_private or
addr.is_loopback or
addr.is_link_local or
addr.is_multicast
)
except ValueError:
return False
2. Bloqueio Proativo de Endereços Internos com ipaddress
A biblioteca nativa ipaddress oferece verificação robusta de faixas de rede privada, eliminando erros comuns em comparações manuais de strings:
import ipaddress
def is_blocked_ip(ip_str: str) -> bool:
try:
ip = ipaddress.ip_address(ip_str)
# Inclui 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.
return ip.is_private or ip.is_loopback or ip.is_link_local
except ValueError:
return True # Inválido = bloqueado
3. Isolamento de Requisições via Sandbox de Rede
Em vez de confiar apenas em lógica aplicada no código, utilize mecanismos de sistema operacional para restringir o alcance das requisições. Exemplos práticos:
- Executar workers de requisição em contêineres Docker com
--network=noneou rede personalizada sem rota para10.0.0.0/8; - Usar
iptablesounftablespara descartar pacotes originados de processos específicos destinados a redes internas; - Configurar proxies HTTP como
mitmproxyouenvoycom políticas de ACL baseadas em SNI e headers.
4. Substituição Segura de Bibliotecas Vulneráveis
Evite requests com verify=False ou allow_redirects=True sem inspeção de redirecionamentos. Prefira alternativas com segurança embutida:
httpxcomlimits=max_connections=10etimeoutrigorosos;urllib3comPoolManagerconfigurado para rejeitar hosts não autorizados viaallowed_methodseallowed_headers;- Bibliotecas especializadas como
httpx-ssrf-guard(extensão com validação em tempo de execução).
5. Monitoramento e Detecção em Tempo Real
Implemente logging estruturado com captura de:
- URL original fornecida pelo usuário;
- Host resolvido após DNS lookup;
- Código de status e tamanho da resposta;
- Tempo de resposta anormal (>3s indica possível scan interno).
Integre com ferramentas como Prometheus + Grafana para alertas automáticos em padrões suspeitos (ex.: múltiplas tentativas de 169.254.169.254 em 60 segundos).