From aaec24b89861d6096a40ee77a73402680e8ca784 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 29 Feb 2024 14:30:24 +0100 Subject: [PATCH] Fix accessing RTMP stream without token or streamkey for anonymous users --- restream/rewrite/rewrite.go | 2 +- rtmp/rtmp.go | 43 ++++++++++++++++++++++++++----------- rtmp/rtmp_test.go | 2 +- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/restream/rewrite/rewrite.go b/restream/rewrite/rewrite.go index 055ca6c9..536232a3 100644 --- a/restream/rewrite/rewrite.go +++ b/restream/rewrite/rewrite.go @@ -124,7 +124,7 @@ func (g *rewrite) rtmpURL(u *url.URL, mode Access, identity iamidentity.Verifier token := identity.GetServiceToken() // Remove the existing token from the path - path, _ := rtmp.GetToken(u) + path, _, _ := rtmp.GetToken(u) u.Path = path q := u.Query() diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 887597bd..9a4f014f 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -203,21 +203,22 @@ func (s *server) log(who, handler, action, resource, message string, client net. }).Log(message) } -// GetToken returns the path without the token and the token found in the URL. If the token -// was part of the path, the token is removed from the path. The token in the query string -// takes precedence. The token in the path is assumed to be the last path element. -func GetToken(u *url.URL) (string, string) { +// GetToken returns the path without the token and the token found in the URL and whether +// it was found in the path. If the token was part of the path, the token is removed from +// the path. The token in the query string takes precedence. The token in the path is +// assumed to be the last path element. +func GetToken(u *url.URL) (string, string, bool) { q := u.Query() if q.Has("token") { // The token was in the query. Return the unmomdified path and the token. - return u.Path, q.Get("token") + return u.Path, q.Get("token"), false } pathElements := splitPath(u.EscapedPath()) nPathElements := len(pathElements) if nPathElements <= 1 { - return u.Path, "" + return u.Path, "", false } rawPath := "/" + strings.Join(pathElements[:nPathElements-1], "/") @@ -234,7 +235,7 @@ func GetToken(u *url.URL) (string, string) { } // Return the path without the token - return path, token + return path, token, true } func splitPath(path string) []string { @@ -261,7 +262,7 @@ func (s *server) handlePlay(conn *rtmp.Conn) { defer conn.Close() remote := conn.NetConn().RemoteAddr() - playpath, token := GetToken(conn.URL) + playpath, token, isStreamkey := GetToken(conn.URL) playpath, _ = removePathPrefix(playpath, s.app) @@ -272,6 +273,11 @@ func (s *server) handlePlay(conn *rtmp.Conn) { return } + if identity == "$anon" && isStreamkey { + // If the token was part of the path, we add it back + playpath = filepath.Join(playpath, token) + } + domain := s.findDomainFromPlaypath(playpath) resource := playpath @@ -384,7 +390,7 @@ func (s *server) handlePublish(conn *rtmp.Conn) { defer conn.Close() remote := conn.NetConn().RemoteAddr() - playpath, token := GetToken(conn.URL) + playpath, token, isStreamkey := GetToken(conn.URL) playpath, app := removePathPrefix(playpath, s.app) @@ -395,6 +401,11 @@ func (s *server) handlePublish(conn *rtmp.Conn) { return } + if identity == "$anon" && isStreamkey { + // If the token was part of the path, we add it back + playpath = filepath.Join(playpath, token) + } + // Check the app patch if app != s.app { s.log(identity, "PUBLISH", "FORBIDDEN", playpath, "invalid app", remote) @@ -483,12 +494,16 @@ func (s *server) findIdentityFromStreamKey(key string) (string, error) { var identity iamidentity.Verifier = nil var err error = nil + var isDefaultIdentity bool = false var token string username, token := enctoken.Unmarshal(key) if len(username) == 0 { + // Legacy compatibility. If the key is only the token, then it + // is assumed that it belongs to the superuser. identity = s.iam.GetDefaultVerifier() + isDefaultIdentity = true } else { identity, err = s.iam.GetVerifier(username) } @@ -500,11 +515,15 @@ func (s *server) findIdentityFromStreamKey(key string) (string, error) { if ok, err := identity.VerifyServiceToken(token); !ok { if err != nil { err = fmt.Errorf("invalid token: %w", err) - } else { - err = fmt.Errorf("invalid token") } - return "$anon", err + if isDefaultIdentity { + // If verifying the token fails and if this is the superuser, + // then assume anonymous access + return "$anon", nil + } + + return identity.Name(), err } return identity.Name(), nil diff --git a/rtmp/rtmp_test.go b/rtmp/rtmp_test.go index f407fa5c..bdb36676 100644 --- a/rtmp/rtmp_test.go +++ b/rtmp/rtmp_test.go @@ -20,7 +20,7 @@ func TestToken(t *testing.T) { u, err := url.Parse(d[0]) require.NoError(t, err) - path, token := GetToken(u) + path, token, _ := GetToken(u) require.Equal(t, d[1], path, "url=%s", u.String()) require.Equal(t, d[2], token, "url=%s", u.String())