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:
Alessandro Ros
2026-07-23 23:06:15 +02:00
committed by GitHub
parent da0691edcc
commit 4c5f558006
9 changed files with 49 additions and 56 deletions
-2
View File
@@ -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
-1
View File
@@ -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{})
-1
View File
@@ -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{})
-1
View File
@@ -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{})
-1
View File
@@ -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{})
+42 -47
View File
@@ -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{
-1
View File
@@ -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{})
-1
View File
@@ -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{})