diff --git a/activitypub/webfinger/webfinger.go b/activitypub/webfinger/webfinger.go index 5cea75c87..14e4cfffb 100644 --- a/activitypub/webfinger/webfinger.go +++ b/activitypub/webfinger/webfinger.go @@ -2,10 +2,13 @@ package webfinger import ( "encoding/json" + "errors" "fmt" "net/http" "net/url" "strings" + + "github.com/owncast/owncast/utils" ) // GetWebfingerLinks will return webfinger data for an account. @@ -18,6 +21,11 @@ func GetWebfingerLinks(account string) ([]map[string]interface{}, error) { accountComponents := strings.Split(account, "@") fediverseServer := accountComponents[1] + // Reject any requests to our internal network or loopback. + if utils.IsHostnameInternal(fediverseServer) { + return nil, errors.New("unable to use provided host as a valid fediverse server") + } + // HTTPS is required. requestURL, err := url.Parse("https://" + fediverseServer) if err != nil { diff --git a/auth/indieauth/client.go b/auth/indieauth/client.go index e3d67e748..9d7a4e736 100644 --- a/auth/indieauth/client.go +++ b/auth/indieauth/client.go @@ -12,6 +12,7 @@ import ( "time" "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/utils" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) @@ -46,10 +47,27 @@ func setupExpiredRequestPruner() { // StartAuthFlow will begin the IndieAuth flow by generating an auth request. func StartAuthFlow(authHost, userID, accessToken, displayName string) (*url.URL, error) { + // Limit the number of pending requests if len(pendingAuthRequests) >= maxPendingRequests { return nil, errors.New("Please try again later. Too many pending requests.") } + // Reject any requests to our internal network or loopback + if utils.IsHostnameInternal(authHost) { + return nil, errors.New("unable to use provided host") + } + + // Santity check the server URL + u, err := url.ParseRequestURI(authHost) + if err != nil { + return nil, errors.New("unable to parse server URL") + } + + // Limit to only secured connections + if u.Scheme != "https" { + return nil, errors.New("only servers secured with https are supported") + } + serverURL := data.GetServerURL() if serverURL == "" { return nil, errors.New("Owncast server URL must be set when using auth") diff --git a/auth/indieauth/server.go b/auth/indieauth/server.go index e97f45737..0d2ddb378 100644 --- a/auth/indieauth/server.go +++ b/auth/indieauth/server.go @@ -40,7 +40,7 @@ type ServerProfileResponse struct { var pendingServerAuthRequests = map[string]ServerAuthRequest{} -const maxPendingRequests = 1000 +const maxPendingRequests = 100 // StartServerAuth will handle the authentication for the admin user of this // Owncast server. Initiated via a GET of the auth endpoint. diff --git a/utils/netutils.go b/utils/netutils.go new file mode 100644 index 000000000..3a9d7e865 --- /dev/null +++ b/utils/netutils.go @@ -0,0 +1,35 @@ +package utils + +import ( + "net" + + log "github.com/sirupsen/logrus" +) + +// IsHostnameInternal will attempt to determine if the hostname is internal to +// this server's network or is the loopback address. +func IsHostnameInternal(hostname string) bool { + // If this is already an IP address don't try to resolve it + if ip := net.ParseIP(hostname); ip != nil { + return isIPAddressInternal(ip) + } + + ips, err := net.LookupIP(hostname) + if err != nil { + // Default to false if we can't resolve the hostname. + log.Debugln("Unable to resolve hostname:", hostname) + return false + } + + for _, ip := range ips { + if isIPAddressInternal(ip) { + return true + } + } + + return false +} + +func isIPAddressInternal(ip net.IP) bool { + return ip.IsLoopback() || ip.IsPrivate() +} diff --git a/utils/netutils_test.go b/utils/netutils_test.go new file mode 100644 index 000000000..9c1d6eded --- /dev/null +++ b/utils/netutils_test.go @@ -0,0 +1,32 @@ +package utils + +import ( + "net" + "testing" +) + +func TestIPAddressInternal(t *testing.T) { + internalLoopbackHost := "localhost" + internalLoopbackHostTest := IsHostnameInternal(internalLoopbackHost) + if !internalLoopbackHostTest { + t.Errorf("IsHostnameInternal(%s) = %v; want true", internalLoopbackHost, internalLoopbackHostTest) + } + + internalLoopbackIP := net.ParseIP("127.0.0.1") + internalLoopbackIPTest := isIPAddressInternal(internalLoopbackIP) + if !internalLoopbackIPTest { + t.Errorf("isIPAddressInternal(%s) = %v; want true", internalLoopbackIP, internalLoopbackIPTest) + } + + externalHost := "example.com" + externalHostTest := IsHostnameInternal(externalHost) + if externalHostTest { + t.Errorf("IsHostnameInternal(%s) = %v; want false", externalHost, externalHostTest) + } + + externalIP := net.ParseIP("93.184.216.34") + externalIPTest := isIPAddressInternal(externalIP) + if externalIPTest { + t.Errorf("isIPAddressInternal(%s) = %v; want false", externalIP, externalIPTest) + } +}