fix: add additional validation before making remote requests (#3398)

This commit is contained in:
Gabe Kangas 2023-10-28 08:15:01 -07:00 committed by GitHub
parent 5406e3d5da
commit a6dbc37a84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 1 deletions

View file

@ -2,10 +2,13 @@ package webfinger
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"github.com/owncast/owncast/utils"
) )
// GetWebfingerLinks will return webfinger data for an account. // GetWebfingerLinks will return webfinger data for an account.
@ -18,6 +21,11 @@ func GetWebfingerLinks(account string) ([]map[string]interface{}, error) {
accountComponents := strings.Split(account, "@") accountComponents := strings.Split(account, "@")
fediverseServer := accountComponents[1] 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. // HTTPS is required.
requestURL, err := url.Parse("https://" + fediverseServer) requestURL, err := url.Parse("https://" + fediverseServer)
if err != nil { if err != nil {

View file

@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/utils"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -46,10 +47,27 @@ func setupExpiredRequestPruner() {
// StartAuthFlow will begin the IndieAuth flow by generating an auth request. // StartAuthFlow will begin the IndieAuth flow by generating an auth request.
func StartAuthFlow(authHost, userID, accessToken, displayName string) (*url.URL, error) { func StartAuthFlow(authHost, userID, accessToken, displayName string) (*url.URL, error) {
// Limit the number of pending requests
if len(pendingAuthRequests) >= maxPendingRequests { if len(pendingAuthRequests) >= maxPendingRequests {
return nil, errors.New("Please try again later. Too many pending requests.") 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() serverURL := data.GetServerURL()
if serverURL == "" { if serverURL == "" {
return nil, errors.New("Owncast server URL must be set when using auth") return nil, errors.New("Owncast server URL must be set when using auth")

View file

@ -40,7 +40,7 @@ type ServerProfileResponse struct {
var pendingServerAuthRequests = map[string]ServerAuthRequest{} var pendingServerAuthRequests = map[string]ServerAuthRequest{}
const maxPendingRequests = 1000 const maxPendingRequests = 100
// StartServerAuth will handle the authentication for the admin user of this // StartServerAuth will handle the authentication for the admin user of this
// Owncast server. Initiated via a GET of the auth endpoint. // Owncast server. Initiated via a GET of the auth endpoint.

35
utils/netutils.go Normal file
View file

@ -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()
}

32
utils/netutils_test.go Normal file
View file

@ -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)
}
}