Estratégias Robustas de Prevenção a SSRF em Aplicações Python

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_url e executa requests.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() ou urllib.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=none ou rede personalizada sem rota para 10.0.0.0/8;
  • Usar iptables ou nftables para descartar pacotes originados de processos específicos destinados a redes internas;
  • Configurar proxies HTTP como mitmproxy ou envoy com 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:

  • httpx com limits=max_connections=10 e timeout rigorosos;
  • urllib3 com PoolManager configurado para rejeitar hosts não autorizados via allowed_methods e allowed_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).

Tags: Python SSRF Web-Security Requests httpx

Publicado em 8-15 06:34