all repos — rastro @ 6533c8851b4a2970689b2081579d5f63f5136a58

BitTorrent tracker!

app/services/ssrf.py (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
from __future__ import annotations

import ipaddress
from ipaddress import IPv4Address, IPv6Address

# AWS/GCP/Azure-style metadata endpoints commonly abused for SSRF
METADATA_HOSTNAMES = frozenset(
    {
        "metadata.google.internal",
        "metadata.goog",
        "kubernetes.default",
        "kubernetes.default.svc",
    }
)


def is_blocked_hostname(hostname: str) -> bool:
    host = hostname.strip(".").lower()
    if host in METADATA_HOSTNAMES:
        return True
    if host.endswith(".local") or host.endswith(".localhost"):
        return True
    return host in {"localhost", "metadata"}


def is_public_ip(ip_str: str) -> bool:
    """Return True if the IP is globally routable / safe to connect to."""
    try:
        ip = ipaddress.ip_address(ip_str)
    except ValueError:
        return False
    return not _is_blocked_ip(ip)


def _is_blocked_ip(ip: IPv4Address | IPv6Address) -> bool:
    if ip.is_loopback:
        return True
    if ip.is_private:
        return True
    if ip.is_link_local:
        return True
    if ip.is_multicast:
        return True
    if ip.is_reserved:
        return True
    if ip.is_unspecified:
        return True
    # CGNAT / documentation / benchmarking often flagged via is_private or reserved;
    # also block classic metadata IPv4 explicitly.
    if isinstance(ip, IPv4Address):
        if ip == IPv4Address("169.254.169.254"):
            return True
        if ip in ipaddress.ip_network("0.0.0.0/8"):
            return True
    if isinstance(ip, IPv6Address):
        # Unique local addresses
        if ip.ipv4_mapped is not None:
            return _is_blocked_ip(ip.ipv4_mapped)
        if (int(ip) >> 118) == 0b1111110:  # fc00::/7
            return True
    return False


def assert_safe_destination(hostname: str, resolved_ips: list[str]) -> None:
    """Raise ValueError if hostname or any resolved IP is unsafe."""
    if is_blocked_hostname(hostname):
        raise ValueError(f"blocked hostname: {hostname}")
    if not resolved_ips:
        raise ValueError("no resolved addresses")
    for ip in resolved_ips:
        if not is_public_ip(ip):
            raise ValueError(f"blocked address: {ip}")


def assert_connected_ip_safe(ip_str: str) -> None:
    """Re-check the address actually used for the connection (anti-rebinding)."""
    if not is_public_ip(ip_str):
        raise ValueError(f"blocked connected address: {ip_str}")