hls, webrtc: prevent cross-origin unauthorized access (#5975)
when a user had previously inserted credentials into a MediaMTX instance through a browser, and AllowOrigins was set to a wildcard, third-party websites visited by the user were allowed to read streams without restrictions. This is now prevented by returning "*" in Access-Control-Allow-Origins when AllowOrigins is a wildcard, a behavior that prevents browsers from sharing credentials with third-party websites.
This commit is contained in:
@@ -179,8 +179,6 @@ After the video tag, add a script that initializes the stream when the page is f
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls({
|
||||
xhrSetup: function (xhr, url) {
|
||||
xhr.withCredentials = true;
|
||||
|
||||
let user = ""; // fill if needed
|
||||
let pass = ""; // fill if needed
|
||||
let token = ""; // fill if needed
|
||||
|
||||
@@ -116,7 +116,6 @@ func TestPreflightRequest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
|
||||
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
|
||||
require.Equal(t, "OPTIONS, GET, POST, PATCH, DELETE", res.Header.Get("Access-Control-Allow-Methods"))
|
||||
require.Equal(t, "Authorization, Content-Type", res.Header.Get("Access-Control-Allow-Headers"))
|
||||
require.Equal(t, byts, []byte{})
|
||||
|
||||
@@ -482,7 +482,6 @@ func TestPreflightRequest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
|
||||
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
|
||||
require.Equal(t, "OPTIONS, GET", res.Header.Get("Access-Control-Allow-Methods"))
|
||||
require.Equal(t, "Authorization", res.Header.Get("Access-Control-Allow-Headers"))
|
||||
require.Equal(t, byts, []byte{})
|
||||
|
||||
@@ -45,7 +45,6 @@ func TestPreflightRequest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
|
||||
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
|
||||
require.Equal(t, "OPTIONS, GET", res.Header.Get("Access-Control-Allow-Methods"))
|
||||
require.Equal(t, "Authorization", res.Header.Get("Access-Control-Allow-Headers"))
|
||||
require.Equal(t, byts, []byte{})
|
||||
|
||||
@@ -44,7 +44,6 @@ func TestPreflightRequest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
|
||||
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
|
||||
require.Equal(t, "OPTIONS, GET", res.Header.Get("Access-Control-Allow-Methods"))
|
||||
require.Equal(t, "Authorization", res.Header.Get("Access-Control-Allow-Headers"))
|
||||
require.Equal(t, byts, []byte{})
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -13,68 +14,63 @@ func isOriginAllowed(origin string, allowOrigins []string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
for _, o := range allowOrigins {
|
||||
if o == "*" {
|
||||
if origin != "" {
|
||||
return origin, true
|
||||
}
|
||||
return "*", true
|
||||
}
|
||||
}
|
||||
|
||||
if origin == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
originURL, err := url.Parse(origin)
|
||||
if err != nil || originURL.Scheme == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if originURL.Port() == "" && originURL.Scheme != "" {
|
||||
switch originURL.Scheme {
|
||||
case "http":
|
||||
originURL.Host = net.JoinHostPort(originURL.Host, "80")
|
||||
case "https":
|
||||
originURL.Host = net.JoinHostPort(originURL.Host, "443")
|
||||
}
|
||||
}
|
||||
|
||||
for _, o := range allowOrigins {
|
||||
allowedURL, errAllowed := url.Parse(o)
|
||||
if errAllowed != nil {
|
||||
continue
|
||||
if origin != "" {
|
||||
originURL, err := url.Parse(origin)
|
||||
if err != nil || originURL.Scheme == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if allowedURL.Port() == "" {
|
||||
switch allowedURL.Scheme {
|
||||
if originURL.Port() == "" && originURL.Scheme != "" {
|
||||
switch originURL.Scheme {
|
||||
case "http":
|
||||
allowedURL.Host = net.JoinHostPort(allowedURL.Host, "80")
|
||||
originURL.Host = net.JoinHostPort(originURL.Host, "80")
|
||||
case "https":
|
||||
allowedURL.Host = net.JoinHostPort(allowedURL.Host, "443")
|
||||
originURL.Host = net.JoinHostPort(originURL.Host, "443")
|
||||
}
|
||||
}
|
||||
|
||||
if allowedURL.Scheme == originURL.Scheme &&
|
||||
allowedURL.Host == originURL.Host &&
|
||||
allowedURL.Port() == originURL.Port() {
|
||||
return origin, true
|
||||
}
|
||||
for _, o := range allowOrigins {
|
||||
allowedURL, errAllowed := url.Parse(o)
|
||||
if errAllowed != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(allowedURL.Host, "*") {
|
||||
pattern := strings.ReplaceAll(allowedURL.Host, "*.", "(.*\\.)?")
|
||||
pattern = strings.ReplaceAll(pattern, "*", ".*")
|
||||
matched, errMatched := regexp.MatchString("^"+pattern+"$", originURL.Host)
|
||||
if errMatched == nil && matched {
|
||||
if allowedURL.Port() == "" {
|
||||
switch allowedURL.Scheme {
|
||||
case "http":
|
||||
allowedURL.Host = net.JoinHostPort(allowedURL.Host, "80")
|
||||
case "https":
|
||||
allowedURL.Host = net.JoinHostPort(allowedURL.Host, "443")
|
||||
}
|
||||
}
|
||||
|
||||
if allowedURL.Scheme == originURL.Scheme &&
|
||||
allowedURL.Host == originURL.Host &&
|
||||
allowedURL.Port() == originURL.Port() {
|
||||
return origin, true
|
||||
}
|
||||
|
||||
if strings.Contains(allowedURL.Host, "*") {
|
||||
pattern := strings.ReplaceAll(allowedURL.Host, "*.", "(.*\\.)?")
|
||||
pattern = strings.ReplaceAll(pattern, "*", ".*")
|
||||
matched, errMatched := regexp.MatchString("^"+pattern+"$", originURL.Host)
|
||||
if errMatched == nil && matched {
|
||||
return origin, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return wildcard as last resort only
|
||||
// because it blocks cross-origin requests with cookies
|
||||
if slices.Contains(allowOrigins, "*") {
|
||||
return "*", true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// add Access-Control-Allow-Origin and Access-Control-Allow-Credentials headers.
|
||||
// add Access-Control-Allow-Origin header.
|
||||
type handlerOrigin struct {
|
||||
h http.Handler
|
||||
allowOrigins []string
|
||||
@@ -84,7 +80,6 @@ func (h *handlerOrigin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
origin, ok := isOriginAllowed(r.Header.Get("Origin"), h.allowOrigins)
|
||||
if ok {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
h.h.ServeHTTP(w, r)
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestHandlerOrigin(t *testing.T) {
|
||||
"everything allowed, with origin",
|
||||
"https://example.com",
|
||||
[]string{"*"},
|
||||
"https://example.com",
|
||||
"*",
|
||||
},
|
||||
{
|
||||
"allowed",
|
||||
@@ -52,6 +52,12 @@ func TestHandlerOrigin(t *testing.T) {
|
||||
[]string{"https://*.example.org"},
|
||||
"https://test.example.org",
|
||||
},
|
||||
{
|
||||
"everything allowed plus specific domain",
|
||||
"https://example.org",
|
||||
[]string{"*", "https://example.org"},
|
||||
"https://example.org",
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
s := &Server{
|
||||
|
||||
@@ -98,7 +98,6 @@ func TestServerPreflightRequest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
|
||||
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
|
||||
require.Equal(t, "OPTIONS, GET", res.Header.Get("Access-Control-Allow-Methods"))
|
||||
require.Equal(t, "Authorization, Range", res.Header.Get("Access-Control-Allow-Headers"))
|
||||
require.Equal(t, byts, []byte{})
|
||||
|
||||
@@ -137,7 +137,6 @@ func TestPreflightRequest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
|
||||
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
|
||||
require.Equal(t, "OPTIONS, GET, POST, PATCH, DELETE", res.Header.Get("Access-Control-Allow-Methods"))
|
||||
require.Equal(t, "Authorization, Content-Type, If-Match", res.Header.Get("Access-Control-Allow-Headers"))
|
||||
require.Equal(t, byts, []byte{})
|
||||
|
||||
Reference in New Issue
Block a user