Skip to content

sec(network): SSRF via DNS rebinding — allowlist checks hostname string, not resolved IP #1176

@chaliy

Description

@chaliy

Summary

The network allowlist checks URL hostnames as literal strings against the allowlist, then relies on reqwest to resolve DNS and make the connection. An attacker who controls a domain's DNS records can point an allowed hostname at internal services (169.254.169.254, 127.0.0.1, internal network IPs) between the allowlist check and the actual connection.

While the allowlist documentation notes this limitation (TM-NET-002), the current mitigation is incomplete: it prevents DNS rebinding where the hostname changes between checks, but does NOT prevent the case where an explicitly allowed hostname resolves to an internal IP.

Threat category: TM-NET (Network Security) — extends TM-NET-002, TM-NET-004
Severity: Medium
Component: `crates/bashkit/src/network/allowlist.rs`, `crates/bashkit/src/network/client.rs`

Root Cause

// allowlist.rs - checks hostname string only
fn matches_pattern(&self, url: &Url, pattern: &str) -> bool {
    // Check host - string comparison only
    match (url.host_str(), pattern_url.host_str()) {
        (Some(url_host), Some(pattern_host)) => {
            if url_host != pattern_host { return false; }
        }
        _ => return false,
    }
    // ... no IP resolution check
}

The client then makes the request, and reqwest resolves the hostname to an IP. If the allowed domain `api.attacker.com` resolves to `169.254.169.254` (AWS metadata), the request proceeds to the metadata service.

Steps to Reproduce

// Attacker controls api.attacker.com DNS
// DNS: api.attacker.com -> 169.254.169.254

let allowlist = NetworkAllowlist::new()
    .allow("https://api.attacker.com");

// This passes the allowlist check (hostname matches)
// but connects to AWS metadata service
let client = HttpClient::new(allowlist);
client.request(Method::Get, 
    "https://api.attacker.com/latest/meta-data/iam/security-credentials/",
    None, &[]).await;
// Returns AWS credentials!

Impact

  • Cloud metadata theft: Access to AWS/GCP/Azure instance metadata (IAM credentials, tokens)
  • Internal service access: Reach internal APIs, databases, admin panels
  • Network scanning: Map internal infrastructure by trying different ports

Acceptance Criteria

  • Add option to block requests to private/reserved IP ranges by default (`NetworkAllowlist::block_private_ips(true)`)
  • Block connections to: 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, fd00::/8
  • Use reqwest's `resolve()` or `dns_resolver()` to inspect resolved IPs before connection
  • Add test: request to hostname resolving to 127.0.0.1 is blocked
  • Add test: request to hostname resolving to 169.254.169.254 is blocked
  • Allow opt-out for legitimate internal-only use cases

Note

The existing threat model acknowledges TM-NET-002 (DNS rebinding) and TM-NET-004 (IP-based bypass). This issue proposes a concrete implementation to close the gap between the threat model and the actual code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions