diff --git a/services/httpservice/client.go b/services/httpservice/client.go index 10b36b6f9f..bac32c1ce5 100644 --- a/services/httpservice/client.go +++ b/services/httpservice/client.go @@ -21,6 +21,8 @@ const ( var reservedIPRanges []*net.IPNet +// IsReservedIP checks whether the target IP belongs to reserved IP address ranges to avoid SSRF attacks to the internal +// network of the Mattermost server func IsReservedIP(ip net.IP) bool { for _, ipRange := range reservedIPRanges { if ipRange.Contains(ip) { @@ -30,6 +32,38 @@ func IsReservedIP(ip net.IP) bool { return false } +// IsOwnIP handles the special case that a request might be made to the public IP of the host which on Linux is routed +// directly via the loopback IP to any listening sockets, effectively bypassing host-based firewalls such as firewalld +func IsOwnIP(ip net.IP) (bool, error) { + interfaces, err := net.Interfaces() + if err != nil { + return false, err + } + + for _, interf := range interfaces { + addresses, err := interf.Addrs() + if err != nil { + return false, err + } + + for _, addr := range addresses { + var selfIP net.IP + switch v := addr.(type) { + case *net.IPNet: + selfIP = v.IP + case *net.IPAddr: + selfIP = v.IP + } + + if ip.Equal(selfIP) { + return true, nil + } + } + } + + return false, nil +} + var defaultUserAgent string func init() { diff --git a/services/httpservice/client_test.go b/services/httpservice/client_test.go index c8beb32cbb..25c4eecd50 100644 --- a/services/httpservice/client_test.go +++ b/services/httpservice/client_test.go @@ -145,3 +145,44 @@ func NewHTTPClient(transport http.RoundTripper) *http.Client { Transport: transport, } } + +func TestIsReservedIP(t *testing.T) { + tests := []struct { + name string + ip net.IP + want bool + }{ + {"127.8.3.5", net.IPv4(127, 8, 3, 5), true}, + {"192.168.0.1", net.IPv4(192, 168, 0, 1), true}, + {"169.254.0.6", net.IPv4(169, 254, 0, 6), true}, + {"127.120.6.3", net.IPv4(127, 120, 6, 3), true}, + {"8.8.8.8", net.IPv4(8, 8, 8, 8), false}, + {"9.9.9.9", net.IPv4(9, 9, 9, 8), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsReservedIP(tt.ip); got != tt.want { + t.Errorf("IsReservedIP() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsOwnIP(t *testing.T) { + tests := []struct { + name string + ip net.IP + want bool + }{ + {"127.0.0.1", net.IPv4(127, 0, 0, 1), true}, + {"8.8.8.8", net.IPv4(8, 0, 0, 8), false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got, _ := IsOwnIP(tt.ip); got != tt.want { + t.Errorf("IsOwnIP() = %v, want %v", got, tt.want) + t.Errorf(tt.ip.String()); + } + }) + } +} diff --git a/services/httpservice/httpservice.go b/services/httpservice/httpservice.go index 8978796293..d47d5d2fcd 100644 --- a/services/httpservice/httpservice.go +++ b/services/httpservice/httpservice.go @@ -67,12 +67,24 @@ func (h *HTTPServiceImpl) MakeTransport(trustURLs bool) http.RoundTripper { } allowIP := func(ip net.IP) bool { - if !IsReservedIP(ip) { + reservedIP := IsReservedIP(ip) + ownIP, err := IsOwnIP(ip) + + // If there is an error getting the self-assigned IPs, default to the secure option + if err != nil { + return false + } + + // If it's not a reserved IP and it's not self-assigned IP, accept the IP + if !reservedIP && !ownIP { return true } + if h.configService.Config().ServiceSettings.AllowedUntrustedInternalConnections == nil { return false } + + // In the case it's the self-assigned IP, enforce that it needs to be explicitly added to the AllowedUntrustedInternalConnections for _, allowed := range strings.Fields(*h.configService.Config().ServiceSettings.AllowedUntrustedInternalConnections) { if _, ipRange, err := net.ParseCIDR(allowed); err == nil && ipRange.Contains(ip) { return true