mirror of
https://github.com/owncast/owncast.git
synced 2024-11-21 20:28:15 +03:00
fix: add additional validation before making remote requests (#3398)
This commit is contained in:
parent
5406e3d5da
commit
a6dbc37a84
5 changed files with 94 additions and 1 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
35
utils/netutils.go
Normal 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
32
utils/netutils_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue