diff --git a/common/util/util.go b/common/util/util.go index f66c69f..37495b6 100644 --- a/common/util/util.go +++ b/common/util/util.go @@ -8,6 +8,7 @@ import ( "net/http" "slices" "sort" + "strings" "github.com/pion/ice/v4" "github.com/pion/sdp/v3" @@ -171,3 +172,22 @@ func GetCandidateAddrs(sdpStr string) []net.IP { } return sortedIpAddr } + +// Checks whether the hostname is local +func IsHostnameLocal(hostname string) bool { + // Per https://en.wikipedia.org/wiki/Special-use_domain_name + tlds := []string{ + ".internal", + ".invalid", + ".local", + ".localhost", + ".onion", + ".test", + } + for _, tld := range tlds { + if strings.HasSuffix(hostname, tld) { + return true + } + } + return hostname == "localhost" +} diff --git a/proxy/lib/proxy-go_test.go b/proxy/lib/proxy-go_test.go index e8e50db..a2e6bd0 100644 --- a/proxy/lib/proxy-go_test.go +++ b/proxy/lib/proxy-go_test.go @@ -506,13 +506,13 @@ func TestUtilityFuncs(t *testing.T) { {pattern: "^snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://faketorproject.net", expects: fmt.Errorf("")}, {pattern: "snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://faketorproject.net", expects: fmt.Errorf("")}, {pattern: "snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://snowflake.torproject.net", expects: nil}, - {pattern: "snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://imaginary-01-snowflake.torproject.net", expects: nil}, - {pattern: "snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://imaginary-aaa-snowflake.torproject.net", expects: nil}, + {pattern: "snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://imaginary-01-snowflake.torproject.net", expects: fmt.Errorf("")}, + {pattern: "snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://imaginary-aaa-snowflake.torproject.net", expects: fmt.Errorf("")}, {pattern: "snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://imaginary-aaa-snowflake.faketorproject.net", expects: fmt.Errorf("")}, {pattern: "^torproject.net$", allowNonTLS: false, targetURL: "wss://faketorproject.net", expects: fmt.Errorf("")}, // Yes, this is how it works if there is no "^". - {pattern: "torproject.net$", allowNonTLS: false, targetURL: "wss://faketorproject.net", expects: nil}, + {pattern: "torproject.net$", allowNonTLS: false, targetURL: "wss://faketorproject.net", expects: fmt.Errorf("")}, // NonTLS {pattern: "snowflake.torproject.net$", allowNonTLS: false, targetURL: "ws://snowflake.torproject.net", expects: fmt.Errorf("")}, @@ -556,8 +556,17 @@ func TestUtilityFuncs(t *testing.T) { {pattern: "$", allowNonTLS: true, targetURL: "//snowflake.torproject.net", expects: fmt.Errorf("")}, {pattern: "$", allowNonTLS: true, targetURL: "/path", expects: fmt.Errorf("")}, {pattern: "$", allowNonTLS: true, targetURL: "wss://snowflake.torproject .net", expects: fmt.Errorf("")}, - {pattern: "$", allowNonTLS: true, targetURL: "wss://😀", expects: nil}, - {pattern: "$", allowNonTLS: true, targetURL: "wss://пример.рф", expects: nil}, + {pattern: "$", allowNonTLS: true, targetURL: "wss://😀", expects: fmt.Errorf("")}, + {pattern: "$", allowNonTLS: true, targetURL: "wss://пример.рф", expects: fmt.Errorf("")}, + + // Local URLs + {pattern: "localhost$", allowNonTLS: false, targetURL: "wss://localhost", expects: fmt.Errorf("")}, + {pattern: "test.internal$", allowNonTLS: false, targetURL: "wss://test.internal", expects: fmt.Errorf("")}, + {pattern: "test.invalid$", allowNonTLS: false, targetURL: "wss://test.invalid", expects: fmt.Errorf("")}, + {pattern: "test.localhost$", allowNonTLS: false, targetURL: "wss://test.localhost", expects: fmt.Errorf("")}, + {pattern: "test.local$", allowNonTLS: false, targetURL: "wss://test.local", expects: fmt.Errorf("")}, + {pattern: "test.onion$", allowNonTLS: false, targetURL: "wss://test.onion", expects: fmt.Errorf("")}, + {pattern: "test.test$", allowNonTLS: false, targetURL: "wss://test.test", expects: fmt.Errorf("")}, // Non-websocket protocols {pattern: "snowflake.torproject.net$", allowNonTLS: false, targetURL: "https://snowflake.torproject.net", expects: fmt.Errorf("")}, diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index bcdfbda..03ced2f 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -712,13 +712,27 @@ func checkIsRelayURLAcceptable( return fmt.Errorf("bad Relay URL %w", err) } if !allowPrivateIPs { - ip := net.ParseIP(parsedRelayURL.Hostname()) + hostname := parsedRelayURL.Hostname() + if util.IsHostnameLocal(hostname) { + return fmt.Errorf("rejected Relay URL: private hostnames are not allowed") + } + ip := net.ParseIP(hostname) // Otherwise it's a domain name, or an invalid IP. if ip != nil { // We should probably use a ready library for this. if !isRemoteAddress(ip) { return fmt.Errorf("rejected Relay URL: private IPs are not allowed") } + } else { + ipArray, err := net.LookupIP(hostname) + if err != nil { + return fmt.Errorf("Could not look up IP %s", hostname) + } + for _, ip := range ipArray { + if !isRemoteAddress(ip) { + return fmt.Errorf("rejected Relay URL: private IPs are not allowed") + } + } } } if !allowNonTLSRelay && parsedRelayURL.Scheme != "wss" {