CaIon 3 недель назад
Родитель
Сommit
e2807c5f95
1 измененных файлов с 71 добавлено и 27 удалено
  1. 71 27
      common/ssrf_protection.go

+ 71 - 27
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 privateNet.Contains(ip) {
-			return true
+	if v4 := ip.To4(); v4 != nil {
+		for _, privateNet := range privateIPv4Nets {
+			if privateNet.Contains(v4) {
+				return true
+			}
 		}
+		return false
 	}
 
-	// 检查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") {
+	// IPv6 检查
+	for _, privateNet := range privateIPv6Nets {
+		if privateNet.Contains(ip) {
 			return true
 		}
 	}
-
+	// 兜底: Go 标准库识别的其他私有地址
+	if ip.IsPrivate() {
+		return true
+	}
 	return false
 }