feat: enhance SSRF protection
This commit is contained in:
+72
-28
@@ -29,45 +29,89 @@ var DefaultSSRFProtection = &SSRFProtection{
|
|||||||
AllowedPorts: []int{},
|
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 {
|
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() {
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
// 接口本地组播 (IPv6 ff01::/16 等)
|
||||||
// 检查私有网段
|
if ip.IsInterfaceLocalMulticast() {
|
||||||
private := []net.IPNet{
|
return true
|
||||||
{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 (保留)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if privateNet.Contains(ip) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 兜底: Go 标准库识别的其他私有地址
|
||||||
// 检查IPv6私有地址
|
if ip.IsPrivate() {
|
||||||
if ip.To4() == nil {
|
return true
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user