Low severityNVD Advisory· Published Oct 16, 2025· Updated Oct 16, 2025
Insecure string comparison enables timing attacks
CVE-2025-54499
Description
Mattermost versions 10.5.x <= 10.5.10, 10.11.x <= 10.11.2 fail to use constant-time comparison for sensitive string comparisons which allows attackers to exploit timing oracles to perform byte-by-byte brute force attacks via response time analysis on Cloud API keys and OAuth client secrets
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/mattermost/mattermost/server/v8Go | < 8.0.0-20250728063359-38208b8f065f | 8.0.0-20250728063359-38208b8f065f |
github.com/mattermost/mattermost-serverGo | >= 10.5.0, < 10.5.11 | 10.5.11 |
github.com/mattermost/mattermost-serverGo | >= 10.11.0, < 10.11.3 | 10.11.3 |
Affected products
1- Range: 10.5.0
Patches
297a4c7839cf5MM-64755: Fix redirect in oauth login (#33388) (#33570)
2 files changed · +181 −6
server/channels/utils/utils.go+9 −6 modified@@ -191,14 +191,17 @@ func AppendQueryParamsToURL(baseURL string, params map[string]string) string { // Validates RedirectURL passed during OAuth or SAML func IsValidWebAuthRedirectURL(config *model.Config, redirectURL string) bool { u, err := url.Parse(redirectURL) - if err == nil && (u.Scheme == "http" || u.Scheme == "https") { - if config.ServiceSettings.SiteURL != nil { - siteURL := *config.ServiceSettings.SiteURL - return strings.Index(strings.ToLower(redirectURL), strings.ToLower(siteURL)) == 0 - } + if err != nil || config.ServiceSettings.SiteURL == nil { + return false + } + siteURL, err := url.Parse(*config.ServiceSettings.SiteURL) + if err != nil { return false } - return true + if u.Scheme == siteURL.Scheme && u.Host == siteURL.Host { + return true + } + return false } // Validates Mobile Custom URL Scheme passed during OAuth or SAML
server/channels/utils/utils_test.go+172 −0 modified@@ -9,6 +9,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/mattermost/mattermost/server/public/model" ) func TestStringArrayIntersection(t *testing.T) { @@ -432,3 +434,173 @@ func TestRoundOffToZeroesResolution(t *testing.T) { }) } } + +func TestIsValidWebAuthRedirectURL(t *testing.T) { + t.Run("Valid redirect URL with matching scheme and host", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "https://example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Valid redirect URL with matching scheme and host with port", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com:8080"), + }, + } + redirectURL := "https://example.com:8080/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Invalid redirect URL with different scheme", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "http://example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid redirect URL with different host", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "https://malicious.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid redirect URL with different port", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com:8080"), + }, + } + redirectURL := "https://example.com:9090/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid redirect URL - malformed URL", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "not-a-valid-url" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid config - nil SiteURL", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: nil, + }, + } + redirectURL := "https://example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid config - malformed SiteURL", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("not-a-valid-url"), + }, + } + redirectURL := "https://example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Valid redirect URL with subdomain", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://app.example.com"), + }, + } + redirectURL := "https://app.example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Invalid redirect URL with different subdomain", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://app.example.com"), + }, + } + redirectURL := "https://api.example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Valid redirect URL with path", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com/mattermost"), + }, + } + redirectURL := "https://example.com/mattermost/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Valid redirect URL with query parameters", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "https://example.com/oauth/callback?state=abc123&code=def456" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Valid redirect URL with fragment", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "https://example.com/oauth/callback#token=abc123" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Invalid redirect URL with @ symbol in host", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://qa-release.test.mattermost.cloud"), + }, + } + redirectURL := "https://qa-release.test.mattermost.cloud@example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) +}
38208b8f065fMM-64755: Fix redirect in oauth login (#33388) (#33569)
2 files changed · +181 −6
server/channels/utils/utils.go+9 −6 modified@@ -191,14 +191,17 @@ func AppendQueryParamsToURL(baseURL string, params map[string]string) string { // Validates RedirectURL passed during OAuth or SAML func IsValidWebAuthRedirectURL(config *model.Config, redirectURL string) bool { u, err := url.Parse(redirectURL) - if err == nil && (u.Scheme == "http" || u.Scheme == "https") { - if config.ServiceSettings.SiteURL != nil { - siteURL := *config.ServiceSettings.SiteURL - return strings.Index(strings.ToLower(redirectURL), strings.ToLower(siteURL)) == 0 - } + if err != nil || config.ServiceSettings.SiteURL == nil { + return false + } + siteURL, err := url.Parse(*config.ServiceSettings.SiteURL) + if err != nil { return false } - return true + if u.Scheme == siteURL.Scheme && u.Host == siteURL.Host { + return true + } + return false } // Validates Mobile Custom URL Scheme passed during OAuth or SAML
server/channels/utils/utils_test.go+172 −0 modified@@ -9,6 +9,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/mattermost/mattermost/server/public/model" ) func TestStringArrayIntersection(t *testing.T) { @@ -432,3 +434,173 @@ func TestRoundOffToZeroesResolution(t *testing.T) { }) } } + +func TestIsValidWebAuthRedirectURL(t *testing.T) { + t.Run("Valid redirect URL with matching scheme and host", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "https://example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Valid redirect URL with matching scheme and host with port", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com:8080"), + }, + } + redirectURL := "https://example.com:8080/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Invalid redirect URL with different scheme", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "http://example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid redirect URL with different host", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "https://malicious.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid redirect URL with different port", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com:8080"), + }, + } + redirectURL := "https://example.com:9090/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid redirect URL - malformed URL", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "not-a-valid-url" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid config - nil SiteURL", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: nil, + }, + } + redirectURL := "https://example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Invalid config - malformed SiteURL", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("not-a-valid-url"), + }, + } + redirectURL := "https://example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Valid redirect URL with subdomain", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://app.example.com"), + }, + } + redirectURL := "https://app.example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Invalid redirect URL with different subdomain", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://app.example.com"), + }, + } + redirectURL := "https://api.example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) + + t.Run("Valid redirect URL with path", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com/mattermost"), + }, + } + redirectURL := "https://example.com/mattermost/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Valid redirect URL with query parameters", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "https://example.com/oauth/callback?state=abc123&code=def456" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Valid redirect URL with fragment", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://example.com"), + }, + } + redirectURL := "https://example.com/oauth/callback#token=abc123" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.True(t, result) + }) + + t.Run("Invalid redirect URL with @ symbol in host", func(t *testing.T) { + config := &model.Config{ + ServiceSettings: model.ServiceSettings{ + SiteURL: model.NewPointer("https://qa-release.test.mattermost.cloud"), + }, + } + redirectURL := "https://qa-release.test.mattermost.cloud@example.com/oauth/callback" + + result := IsValidWebAuthRedirectURL(config, redirectURL) + assert.False(t, result) + }) +}
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
5- github.com/advisories/GHSA-xr3w-rmvj-f6m7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-54499ghsaADVISORY
- github.com/mattermost/mattermost/commit/38208b8f065f0786eac0e968f9d754b91b62878cghsaWEB
- github.com/mattermost/mattermost/commit/97a4c7839cf5610cfe17c52042878aebb7678372ghsaWEB
- mattermost.com/security-updatesghsaWEB
News mentions
0No linked articles in our index yet.