High severityNVD Advisory· Published Oct 16, 2025· Updated Feb 26, 2026
Arbitrary Mattermost Team can be joined by manipulating the OAuth state
CVE-2025-58073
Description
Mattermost versions 10.11.x <= 10.11.1, 10.10.x <= 10.10.2, 10.5.x <= 10.5.10 fail to verify a user has permission to join a Mattermost team using the original invite token which allows any attacked to join any team on a Mattermost server regardless of restrictions via manipulating the OAuth state.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/mattermost/mattermost/server/v8Go | < 8.0.0-20250807174701-e14175eb6539 | 8.0.0-20250807174701-e14175eb6539 |
github.com/mattermost/mattermost-serverGo | >= 10.11.0, < 10.11.2 | 10.11.2 |
github.com/mattermost/mattermost-serverGo | >= 10.10.0, < 10.10.3 | 10.10.3 |
github.com/mattermost/mattermost-serverGo | >= 10.5.0, < 10.5.11 | 10.5.11 |
Affected products
1- Range: 10.11.0
Patches
339bd251fe4f6[MM-65015] Restore Mobile redirection on oauth login (#33626) (#33637)
3 files changed · +21 −6
server/channels/web/oauth.go+16 −4 modified@@ -11,6 +11,7 @@ import ( "net/url" "path" "path/filepath" + "slices" "strings" "time" @@ -23,6 +24,10 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/utils/fileutils" ) +const ( + callbackHost = "callback" +) + func (w *Web) InitOAuth() { // API version independent OAuth 2.0 as a service provider endpoints w.MainRouter.Handle("/oauth/authorize", w.APIHandlerTrustRequester(authorizeOAuthPage)).Methods(http.MethodGet) @@ -316,7 +321,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { hasRedirectURL = redirectURL != "" } } - redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL) + redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL, c.App.Config().NativeAppSettings.AppCustomURLSchemes) renderError := func(err *model.AppError) { if isMobile && hasRedirectURL { @@ -536,7 +541,7 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, authURL, http.StatusFound) } -func fullyQualifiedRedirectURL(siteURLPrefix, targetURL string) string { +func fullyQualifiedRedirectURL(siteURLPrefix, targetURL string, otherValidSchemes []string) string { parsed, err := url.Parse(targetURL) if err != nil { return siteURLPrefix @@ -545,8 +550,15 @@ func fullyQualifiedRedirectURL(siteURLPrefix, targetURL string) string { if err != nil { return siteURLPrefix } - - // Check if the targetURL is a valid URL and is within the siteURLPrefix + // mobile access + if slices.Contains(otherValidSchemes, fmt.Sprintf("%v://", parsed.Scheme)) && + parsed.Host == callbackHost && + parsed.Path == "" && + parsed.RawQuery == "" && + parsed.Fragment == "" { + return targetURL + } + // Check if the targetURL is valid and within the siteURLPrefix, excluding native app schemes like mmauth:// sameScheme := parsed.Scheme == prefixParsed.Scheme sameHost := parsed.Host == prefixParsed.Host safePath := strings.HasPrefix(path.Clean(parsed.Path), path.Clean(prefixParsed.Path))
server/channels/web/oauth_test.go+4 −1 modified@@ -849,6 +849,7 @@ func (th *TestHelper) AddPermissionToRole(permission string, roleName string) { func TestFullyQualifiedRedirectURL(t *testing.T) { const siteURL = "https://xxx.yyy/mm" + for target, expected := range map[string]string{ "": siteURL, "/": siteURL + "/", @@ -869,9 +870,11 @@ func TestFullyQualifiedRedirectURL(t *testing.T) { "https://xxx.yyy/mm/some-path#section": siteURL + "/some-path#section", "https://xxx.yyy/mm/../malicious-path": siteURL, ":foo": siteURL, + "mmauth://callback": "mmauth://callback", + "mmauth://xxx.yyy/mm": siteURL, // invalid mobile URL (wrong host) } { t.Run(target, func(t *testing.T) { - require.Equal(t, expected, fullyQualifiedRedirectURL(siteURL, target)) + require.Equal(t, expected, fullyQualifiedRedirectURL(siteURL, target, []string{"mmauth://"})) }) } }
server/channels/web/saml.go+1 −1 modified@@ -117,7 +117,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { redirectURL = val hasRedirectURL = val != "" } - redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL) + redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL, c.App.Config().NativeAppSettings.AppCustomURLSchemes) handleError := func(err *model.AppError) { if isMobile && hasRedirectURL {
2096f975b2c0[MM-65015] Restore Mobile redirection on oauth login (#33626) (#33634)
3 files changed · +21 −6
server/channels/web/oauth.go+16 −4 modified@@ -11,6 +11,7 @@ import ( "net/url" "path" "path/filepath" + "slices" "strings" "time" @@ -23,6 +24,10 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/utils/fileutils" ) +const ( + callbackHost = "callback" +) + func (w *Web) InitOAuth() { // API version independent OAuth 2.0 as a service provider endpoints w.MainRouter.Handle("/oauth/authorize", w.APIHandlerTrustRequester(authorizeOAuthPage)).Methods(http.MethodGet) @@ -316,7 +321,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { hasRedirectURL = redirectURL != "" } } - redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL) + redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL, c.App.Config().NativeAppSettings.AppCustomURLSchemes) renderError := func(err *model.AppError) { if isMobile && hasRedirectURL { @@ -536,7 +541,7 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, authURL, http.StatusFound) } -func fullyQualifiedRedirectURL(siteURLPrefix, targetURL string) string { +func fullyQualifiedRedirectURL(siteURLPrefix, targetURL string, otherValidSchemes []string) string { parsed, err := url.Parse(targetURL) if err != nil { return siteURLPrefix @@ -545,8 +550,15 @@ func fullyQualifiedRedirectURL(siteURLPrefix, targetURL string) string { if err != nil { return siteURLPrefix } - - // Check if the targetURL is a valid URL and is within the siteURLPrefix + // mobile access + if slices.Contains(otherValidSchemes, fmt.Sprintf("%v://", parsed.Scheme)) && + parsed.Host == callbackHost && + parsed.Path == "" && + parsed.RawQuery == "" && + parsed.Fragment == "" { + return targetURL + } + // Check if the targetURL is valid and within the siteURLPrefix, excluding native app schemes like mmauth:// sameScheme := parsed.Scheme == prefixParsed.Scheme sameHost := parsed.Host == prefixParsed.Host safePath := strings.HasPrefix(path.Clean(parsed.Path), path.Clean(prefixParsed.Path))
server/channels/web/oauth_test.go+4 −1 modified@@ -861,6 +861,7 @@ func (th *TestHelper) AddPermissionToRole(permission string, roleName string) { func TestFullyQualifiedRedirectURL(t *testing.T) { const siteURL = "https://xxx.yyy/mm" + for target, expected := range map[string]string{ "": siteURL, "/": siteURL + "/", @@ -881,9 +882,11 @@ func TestFullyQualifiedRedirectURL(t *testing.T) { "https://xxx.yyy/mm/some-path#section": siteURL + "/some-path#section", "https://xxx.yyy/mm/../malicious-path": siteURL, ":foo": siteURL, + "mmauth://callback": "mmauth://callback", + "mmauth://xxx.yyy/mm": siteURL, // invalid mobile URL (wrong host) } { t.Run(target, func(t *testing.T) { - require.Equal(t, expected, fullyQualifiedRedirectURL(siteURL, target)) + require.Equal(t, expected, fullyQualifiedRedirectURL(siteURL, target, []string{"mmauth://"})) }) } }
server/channels/web/saml.go+1 −1 modified@@ -118,7 +118,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { redirectURL = val hasRedirectURL = val != "" } - redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL) + redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL, c.App.Config().NativeAppSettings.AppCustomURLSchemes) handleError := func(err *model.AppError) { if isMobile && hasRedirectURL {
e14175eb6539[MM-65015] Restore Mobile redirection on oauth login (#33626) (#33633)
3 files changed · +21 −6
server/channels/web/oauth.go+16 −4 modified@@ -11,6 +11,7 @@ import ( "net/url" "path" "path/filepath" + "slices" "strings" "time" @@ -22,6 +23,10 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/utils/fileutils" ) +const ( + callbackHost = "callback" +) + func (w *Web) InitOAuth() { // API version independent OAuth 2.0 as a service provider endpoints w.MainRouter.Handle("/oauth/authorize", w.APIHandlerTrustRequester(authorizeOAuthPage)).Methods(http.MethodGet) @@ -315,7 +320,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { hasRedirectURL = redirectURL != "" } } - redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL) + redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL, c.App.Config().NativeAppSettings.AppCustomURLSchemes) renderError := func(err *model.AppError) { if isMobile && hasRedirectURL { @@ -535,7 +540,7 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, authURL, http.StatusFound) } -func fullyQualifiedRedirectURL(siteURLPrefix, targetURL string) string { +func fullyQualifiedRedirectURL(siteURLPrefix, targetURL string, otherValidSchemes []string) string { parsed, err := url.Parse(targetURL) if err != nil { return siteURLPrefix @@ -544,8 +549,15 @@ func fullyQualifiedRedirectURL(siteURLPrefix, targetURL string) string { if err != nil { return siteURLPrefix } - - // Check if the targetURL is a valid URL and is within the siteURLPrefix + // mobile access + if slices.Contains(otherValidSchemes, fmt.Sprintf("%v://", parsed.Scheme)) && + parsed.Host == callbackHost && + parsed.Path == "" && + parsed.RawQuery == "" && + parsed.Fragment == "" { + return targetURL + } + // Check if the targetURL is valid and within the siteURLPrefix, excluding native app schemes like mmauth:// sameScheme := parsed.Scheme == prefixParsed.Scheme sameHost := parsed.Host == prefixParsed.Host safePath := strings.HasPrefix(path.Clean(parsed.Path), path.Clean(prefixParsed.Path))
server/channels/web/oauth_test.go+4 −1 modified@@ -861,6 +861,7 @@ func (th *TestHelper) AddPermissionToRole(permission string, roleName string) { func TestFullyQualifiedRedirectURL(t *testing.T) { const siteURL = "https://xxx.yyy/mm" + for target, expected := range map[string]string{ "": siteURL, "/": siteURL + "/", @@ -881,9 +882,11 @@ func TestFullyQualifiedRedirectURL(t *testing.T) { "https://xxx.yyy/mm/some-path#section": siteURL + "/some-path#section", "https://xxx.yyy/mm/../malicious-path": siteURL, ":foo": siteURL, + "mmauth://callback": "mmauth://callback", + "mmauth://xxx.yyy/mm": siteURL, // invalid mobile URL (wrong host) } { t.Run(target, func(t *testing.T) { - require.Equal(t, expected, fullyQualifiedRedirectURL(siteURL, target)) + require.Equal(t, expected, fullyQualifiedRedirectURL(siteURL, target, []string{"mmauth://"})) }) } }
server/channels/web/saml.go+1 −1 modified@@ -117,7 +117,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { redirectURL = val hasRedirectURL = val != "" } - redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL) + redirectURL = fullyQualifiedRedirectURL(c.GetSiteURLHeader(), redirectURL, c.App.Config().NativeAppSettings.AppCustomURLSchemes) handleError := func(err *model.AppError) { if isMobile && hasRedirectURL {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-6q7m-p8cc-998rghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-58073ghsaADVISORY
- github.com/mattermost/mattermost/commit/2096f975b2c0ebe95fb1078c3b1a527da574796dghsaWEB
- github.com/mattermost/mattermost/commit/39bd251fe4f66b7e847fc6d653221886347ff160ghsaWEB
- github.com/mattermost/mattermost/commit/e14175eb65393bebc16dbb68a8105b3094b0f0ddghsaWEB
- mattermost.com/security-updatesghsaWEB
News mentions
0No linked articles in our index yet.