|
|
@@ -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
|
|
|
}
|
|
|
|