flawopen.com/ssrf/Swift

● CWE-918 · Alta
Pesquisa · FlawOpen

Server-Side Request Forgery in Swift

How URLSession allows internal IP queries in Swift/Vapor, and how to enforce IP boundaries.

💡 Explicação em Linguagem Simples (ELI5)

Imagine enviar um assistente de escritório para buscar um pacote em uma loja pública, mas fornecendo o endereço do cofre trancado na sala da gerência. Como o assistente tem um crachá interno, ele abre o cofre e entrega segredos da empresa.

Conceitos Centrais e Termos

Web Application Security
Componente de arquitetura principal afetado pelo CWE-918.
CWE-918
Classificação padrão Common Weakness Enumeration (CWE) para ssrf-swift.
Defense-in-Depth
Verificação de engenharia em múltiplas camadas e isolamento de limites em tempo de execução.

Fluxo de Ataque Passo a Paso

Step 1

Ingestão de URL Remota Não Confiável

O aplicativo recebe uma URL de usuário para baixar webhooks, buscar avatares ou gerar relatórios PDF.

Step 2

Endereçamento de Rede Interna

O invasor fornece um endereço local ou do serviço de metadados de nuvem (ex: http://169.254.169.254/latest/meta-data/).

Step 3

Egress de Soquete Irrestrito

O cliente HTTP do servidor inicia a conexão de dentro da VPC privada sem validar o IP resolvido ou sub-rede.

Step 4

Exfiltração de Identidade e Credenciais em Nuvem

O serviço interno de metadados confia no IP do servidor e retorna credenciais temporárias de IAM ou tokens de acesso.

Código-Fonte: Vulnerável vs. Seguro

✕ IMPLEMENTAÇÃO VULNERÁVEL
// VULNERABLE: Unchecked reqwest::get
use reqwest;

async fn fetch_remote_resource(user_url: &str) -> Result<Vec<u8>, reqwest::Error> {
    // Attacker passes: http://169.254.169.254/latest/meta-data/
    let resp = reqwest::get(user_url).await?;
    let bytes = resp.bytes().await?;
    Ok(bytes.to_vec())
}
✓ PATCH SEGURO E ROBUSTO
// HARDENED: Resolve DNS, assert non-private IP, and disable redirects
use std::net::{IpAddr, ToSocketAddrs};
use reqwest::redirect::Policy;
use url::Url;

fn is_safe_ip(ip: IpAddr) -> bool {
    match ip {
        IpAddr::V4(ipv4) => {
            !ipv4.is_loopback() &&
            !ipv4.is_private() &&
            !ipv4.is_link_local() &&
            !ipv4.is_unspecified()
        }
        IpAddr::V6(ipv6) => {
            !ipv6.is_loopback() &&
            !ipv6.is_unspecified()
        }
    }
}

async fn fetch_remote_resource(user_url: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let parsed_url = Url::parse(user_url)?;
    if parsed_url.scheme() != "http" && parsed_url.scheme() != "https" {
        return Err("Invalid URL scheme".into());
    }
    
    let host = parsed_url.host_str().ok_or("Missing host")?;
    let port = parsed_url.port_or_known_default().unwrap_or(80);
    
    // 1. Resolve host and inspect all IP addresses
    let socket_addrs: Vec<_> = format!("{}:{}", host, port).to_socket_addrs()?.collect();
    for sa in &socket_addrs {
        if !is_safe_ip(sa.ip()) {
            return Err("Access to private/internal IP rejected".into());
        }
    }
    
    // 2. Build client with redirects disabled
    let client = reqwest::Client::builder()
        .redirect(Policy::none())
        .timeout(std::time::Duration::from_secs(3))
        .build()?;
        
    let resp = client.get(parsed_url).send().await?;
    let bytes = resp.bytes().await?;
    Ok(bytes.to_vec())
}

Lista de Verificação de Segurança para Engenharia

References