diff --git a/common/ssrf_protection.go b/common/ssrf_protection.go index 3cd5c2ea..1e0c00d6 100644 --- a/common/ssrf_protection.go +++ b/common/ssrf_protection.go @@ -29,45 +29,89 @@ var DefaultSSRFProtection = &SSRFProtection{ AllowedPorts: []int{}, } -// isPrivateIP 检查IP是否为私有地址 +// privateIPv4Nets IPv4 私有/保留/特殊用途网段 +// 参考 IANA IPv4 Special-Purpose Address Registry +// https://www.iana.org/assignments/iana-ipv4-special-registry/ +var privateIPv4Nets = []net.IPNet{ + {IP: net.IPv4(0, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // 0.0.0.0/8 ("This network" / 未指定) + {IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // 10.0.0.0/8 (私有) + {IP: net.IPv4(100, 64, 0, 0), Mask: net.CIDRMask(10, 32)}, // 100.64.0.0/10 (运营商级 NAT / CGNAT) + {IP: net.IPv4(127, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // 127.0.0.0/8 (回环) + {IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)}, // 169.254.0.0/16 (链路本地) + {IP: net.IPv4(172, 16, 0, 0), Mask: net.CIDRMask(12, 32)}, // 172.16.0.0/12 (私有) + {IP: net.IPv4(192, 0, 0, 0), Mask: net.CIDRMask(24, 32)}, // 192.0.0.0/24 (IETF 协议分配) + {IP: net.IPv4(192, 0, 2, 0), Mask: net.CIDRMask(24, 32)}, // 192.0.2.0/24 (TEST-NET-1) + {IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(16, 32)}, // 192.168.0.0/16 (私有) + {IP: net.IPv4(198, 18, 0, 0), Mask: net.CIDRMask(15, 32)}, // 198.18.0.0/15 (基准测试) + {IP: net.IPv4(198, 51, 100, 0), Mask: net.CIDRMask(24, 32)}, // 198.51.100.0/24 (TEST-NET-2) + {IP: net.IPv4(203, 0, 113, 0), Mask: net.CIDRMask(24, 32)}, // 203.0.113.0/24 (TEST-NET-3) + {IP: net.IPv4(224, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // 224.0.0.0/4 (组播) + {IP: net.IPv4(240, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // 240.0.0.0/4 (保留) + {IP: net.IPv4(255, 255, 255, 255), Mask: net.CIDRMask(32, 32)}, // 255.255.255.255/32 (受限广播) +} + +// privateIPv6Nets IPv6 私有/保留/特殊用途网段 +// 参考 IANA IPv6 Special-Purpose Address Registry +// https://www.iana.org/assignments/iana-ipv6-special-registry/ +var privateIPv6Nets = func() []net.IPNet { + cidrs := []string{ + "::/128", // 未指定地址 + "::1/128", // 回环 + "::ffff:0:0/96", // IPv4-mapped + "64:ff9b::/96", // IPv4/IPv6 translation + "100::/64", // Discard-Only + "2001::/23", // IETF Protocol Assignments + "2001:db8::/32", // 文档 + "fc00::/7", // Unique Local Address (ULA) + "fe80::/10", // 链路本地 + "ff00::/8", // 组播 + } + nets := make([]net.IPNet, 0, len(cidrs)) + for _, c := range cidrs { + if _, n, err := net.ParseCIDR(c); err == nil && n != nil { + nets = append(nets, *n) + } + } + return nets +}() + +// isPrivateIP 检查IP是否为私有/保留/特殊用途地址 func isPrivateIP(ip net.IP) bool { + if ip == nil { + return true + } + // 未指定地址 (0.0.0.0, ::) + if ip.IsUnspecified() { + return true + } + // 回环、链路本地 (unicast/multicast) if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { return true } - - // 检查私有网段 - private := []net.IPNet{ - {IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // 10.0.0.0/8 - {IP: net.IPv4(172, 16, 0, 0), Mask: net.CIDRMask(12, 32)}, // 172.16.0.0/12 - {IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(16, 32)}, // 192.168.0.0/16 - {IP: net.IPv4(127, 0, 0, 0), Mask: net.CIDRMask(8, 32)}, // 127.0.0.0/8 - {IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)}, // 169.254.0.0/16 (链路本地) - {IP: net.IPv4(224, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // 224.0.0.0/4 (组播) - {IP: net.IPv4(240, 0, 0, 0), Mask: net.CIDRMask(4, 32)}, // 240.0.0.0/4 (保留) + // 接口本地组播 (IPv6 ff01::/16 等) + if ip.IsInterfaceLocalMulticast() { + return true } - for _, privateNet := range private { + if v4 := ip.To4(); v4 != nil { + for _, privateNet := range privateIPv4Nets { + if privateNet.Contains(v4) { + return true + } + } + return false + } + + // IPv6 检查 + for _, privateNet := range privateIPv6Nets { if privateNet.Contains(ip) { return true } } - - // 检查IPv6私有地址 - if ip.To4() == nil { - // IPv6 loopback - if ip.Equal(net.IPv6loopback) { - return true - } - // IPv6 link-local - if strings.HasPrefix(ip.String(), "fe80:") { - return true - } - // IPv6 unique local - if strings.HasPrefix(ip.String(), "fc") || strings.HasPrefix(ip.String(), "fd") { - return true - } + // 兜底: Go 标准库识别的其他私有地址 + if ip.IsPrivate() { + return true } - return false }