fix: block link-local addresses in SSRF protection

This commit is contained in:
Elias Schneider
2026-07-13 11:05:24 +02:00
parent a3f27ec2ec
commit 9714296efb
2 changed files with 55 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"net" "net"
"net/netip"
"net/url" "net/url"
"strings" "strings"
@@ -56,7 +57,17 @@ func IsTailscaleIP(ip net.IP) bool {
} }
func IsPrivateIP(ip net.IP) bool { func IsPrivateIP(ip net.IP) bool {
return IsLocalhostIP(ip) || IsPrivateLanIP(ip) || IsTailscaleIP(ip) || IsLocalIPv6(ip) if IsLocalhostIP(ip) || IsPrivateLanIP(ip) || IsTailscaleIP(ip) || IsLocalIPv6(ip) {
return true
}
addr, ok := netip.AddrFromSlice(ip)
if !ok {
return false
}
addr = addr.Unmap()
return addr.IsLoopback() || addr.IsPrivate() || addr.IsLinkLocalUnicast() || addr.IsLinkLocalMulticast() || addr.IsUnspecified()
} }
func IsURLPrivate(ctx context.Context, u *url.URL) (bool, error) { func IsURLPrivate(ctx context.Context, u *url.URL) (bool, error) {

View File

@@ -110,12 +110,19 @@ func TestIsPrivateIP(t *testing.T) {
ip string ip string
expected bool expected bool
}{ }{
{"127.0.0.1", true}, // localhost {"127.0.0.1", true}, // localhost
{"192.168.1.1", true}, // private LAN {"192.168.1.1", true}, // private LAN
{"100.64.0.1", true}, // Tailscale {"100.64.0.1", true}, // Tailscale
{"fd00::1", true}, // local IPv6 {"169.254.169.254", true}, // IPv4 link-local
{"8.8.8.8", false}, // public IPv4 {"169.254.170.2", true}, // IPv4 link-local
{"2001:4860:4860::8888", false}, // public IPv6 {"::ffff:169.254.169.254", true}, // IPv4-mapped link-local
{"fe80::1", true}, // IPv6 link-local
{"ff02::1", true}, // IPv6 link-local multicast
{"0.0.0.0", true}, // IPv4 unspecified
{"::", true}, // IPv6 unspecified
{"fd00::1", true}, // private IPv6
{"8.8.8.8", false}, // public IPv4
{"2001:4860:4860::8888", false}, // public IPv6
} }
for _, tt := range tests { for _, tt := range tests {
@@ -231,6 +238,36 @@ func TestIsURLPrivate(t *testing.T) {
expectPriv: true, expectPriv: true,
expectError: false, expectError: false,
}, },
{
name: "IPv4 link-local metadata IP",
urlStr: "http://169.254.169.254",
expectPriv: true,
expectError: false,
},
{
name: "IPv4-mapped link-local metadata IP",
urlStr: "http://[::ffff:169.254.169.254]",
expectPriv: true,
expectError: false,
},
{
name: "IPv6 link-local IP",
urlStr: "http://[fe80::1]",
expectPriv: true,
expectError: false,
},
{
name: "IPv4 unspecified IP",
urlStr: "http://0.0.0.0",
expectPriv: true,
expectError: false,
},
{
name: "IPv6 unspecified IP",
urlStr: "http://[::]",
expectPriv: true,
expectError: false,
},
{ {
name: "public IP - Google DNS", name: "public IP - Google DNS",
urlStr: "http://8.8.8.8", urlStr: "http://8.8.8.8",