Low severityNVD Advisory· Published Oct 28, 2024· Updated Oct 28, 2024
Incorrect Session Creation with Desktop SSO
CVE-2024-10214
Description
Mattermost versions 9.11.X <= 9.11.1, 9.5.x <= 9.5.9 icorrectly issues two sessions when using desktop SSO - one in the browser and one in desktop with incorrect settings.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/mattermost/mattermost/server/v8Go | < 8.0.0-20240821220019-0d6b1070a26f | 8.0.0-20240821220019-0d6b1070a26f |
Affected products
1- Range: 9.11.0
Patches
10d6b1070a26f[MM-58549] Desktop login (#27732)
6 files changed · +157 −42
server/channels/api4/user.go+9 −1 modified@@ -1979,7 +1979,15 @@ func loginWithDesktopToken(c *Context, w http.ResponseWriter, r *http.Request) { return } - session, err := c.App.DoLogin(c.AppContext, w, r, user, deviceId, false, false, false) + isOAuthUser := user.IsOAuthUser() + isSamlUser := user.IsSAMLUser() + + if !isOAuthUser && !isSamlUser { + c.Err = model.NewAppError("loginWithDesktopToken", "api.user.login_with_desktop_token.not_oauth_or_saml_user.app_error", nil, "", http.StatusUnauthorized) + return + } + + session, err := c.App.DoLogin(c.AppContext, w, r, user, deviceId, false, isOAuthUser, isSamlUser) if err != nil { c.Err = err return
server/channels/api4/user_test.go+72 −0 modified@@ -7776,3 +7776,75 @@ func TestUserUpdateEvents(t *testing.T) { }) }) } + +func TestLoginWithDesktopToken(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + t.Run("login SAML User with desktop token", func(t *testing.T) { + samlUser := th.CreateUserWithAuth(model.UserAuthServiceSaml) + + token, appErr := th.App.GenerateAndSaveDesktopToken(time.Now().Unix(), samlUser) + assert.Nil(t, appErr) + + user, _, err := th.Client.LoginWithDesktopToken(context.Background(), *token, "") + require.NoError(t, err) + assert.Equal(t, samlUser.Id, user.Id) + + sessions, _, err := th.SystemAdminClient.GetSessions(context.Background(), samlUser.Id, "") + require.NoError(t, err) + + assert.Len(t, sessions, 1) + assert.Equal(t, "true", sessions[0].Props["isSaml"]) + assert.Equal(t, "false", sessions[0].Props["isOAuthUser"]) + }) + + t.Run("login OAuth User with desktop token", func(t *testing.T) { + gitlabUser := th.CreateUserWithAuth(model.UserAuthServiceGitlab) + + token, appErr := th.App.GenerateAndSaveDesktopToken(time.Now().Unix(), gitlabUser) + assert.Nil(t, appErr) + + user, _, err := th.Client.LoginWithDesktopToken(context.Background(), *token, "") + require.NoError(t, err) + assert.Equal(t, gitlabUser.Id, user.Id) + + sessions, _, err := th.SystemAdminClient.GetSessions(context.Background(), gitlabUser.Id, "") + require.NoError(t, err) + + assert.Len(t, sessions, 1) + assert.Equal(t, "false", sessions[0].Props["isSaml"]) + assert.Equal(t, "true", sessions[0].Props["isOAuthUser"]) + }) + + t.Run("login email user with desktop token", func(t *testing.T) { + // Sleep to avoid rate limit error + time.Sleep(time.Second) + user := th.CreateUser() + + token, appErr := th.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user) + assert.Nil(t, appErr) + + _, resp, err := th.Client.LoginWithDesktopToken(context.Background(), *token, "") + require.Error(t, err) + CheckUnauthorizedStatus(t, resp) + }) + + t.Run("invalid desktop token on login", func(t *testing.T) { + user := th.CreateUser() + + _, appErr := th.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user) + assert.Nil(t, appErr) + + invalidToken := "testinvalidToken" + token := &invalidToken + + _, _, err := th.Client.LoginWithDesktopToken(context.Background(), *token, "") + require.Error(t, err) + + sessions, _, err := th.SystemAdminClient.GetSessions(context.Background(), user.Id, "") + require.NoError(t, err) + + assert.Len(t, sessions, 0) + }) +}
server/channels/web/oauth.go+39 −29 modified@@ -339,7 +339,45 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } else if action == model.OAuthActionSSOToEmail { redirectURL = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"]) } else { - session, err := c.App.DoLogin(c.AppContext, w, r, user, "", isMobile, false, false) + desktopToken := "" + if val, ok := props["desktop_token"]; ok { + desktopToken = val + } + + // If it's a desktop login we generate a token and redirect to another endpoint to handle session creation + if desktopToken != "" { + serverToken, serverTokenErr := c.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user) + if serverTokenErr != nil { + serverTokenErr.Translate(c.AppContext.T) + c.LogErrorByCode(serverTokenErr) + renderError(serverTokenErr) + return + } + + queryString := map[string]string{ + "client_token": desktopToken, + "server_token": *serverToken, + } + if val, ok := props["redirect_to"]; ok { + queryString["redirect_to"] = val + } + if strings.HasPrefix(desktopToken, "dev-") { + queryString["isDesktopDev"] = "true" + } + + redirectURL = utils.AppendQueryParamsToURL(c.GetSiteURLHeader()+"/login/desktop", queryString) + + auditRec.Success() + c.LogAudit("success") + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect) + return + } + + isOAuthUser := user.IsOAuthUser() + + session, err := c.App.DoLogin(c.AppContext, w, r, user, "", isMobile, isOAuthUser, false) if err != nil { err.Translate(c.AppContext.T) c.Logger.Error(err.Error()) @@ -372,34 +410,6 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } // For web c.App.AttachSessionCookies(c.AppContext, w, r) - - desktopToken := "" - if val, ok := props["desktop_token"]; ok { - desktopToken = val - } - - if desktopToken != "" { - serverToken, serverTokenErr := c.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user) - if serverTokenErr != nil { - serverTokenErr.Translate(c.AppContext.T) - c.LogErrorByCode(serverTokenErr) - renderError(serverTokenErr) - return - } - - queryString := map[string]string{ - "client_token": desktopToken, - "server_token": *serverToken, - } - if val, ok := props["redirect_to"]; ok { - queryString["redirect_to"] = val - } - if strings.HasPrefix(desktopToken, "dev-") { - queryString["isDesktopDev"] = "true" - } - - redirectURL = utils.AppendQueryParamsToURL(c.GetSiteURLHeader()+"/login/desktop", queryString) - } } auditRec.Success()
server/channels/web/saml.go+14 −12 modified@@ -178,19 +178,9 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { auditRec.AddMeta("obtained_user_id", user.Id) c.LogAuditWithUserId(user.Id, "obtained user") - session, err := c.App.DoLogin(c.AppContext, w, r, user, "", isMobile, false, true) - if err != nil { - handleError(err) - return - } - c.AppContext = c.AppContext.WithSession(session) - - auditRec.Success() - c.LogAuditWithUserId(user.Id, "success") - - c.App.AttachSessionCookies(c.AppContext, w, r) - desktopToken := relayProps["desktop_token"] + + // If it's a desktop login we generate a token and redirect to another endpoint to handle session creation if desktopToken != "" { serverToken, serverTokenErr := c.App.GenerateAndSaveDesktopToken(time.Now().Unix(), user) if serverTokenErr != nil { @@ -214,6 +204,18 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { return } + // If it's not a desktop login we create a session for this SAML User that will be used in their browser or mobile app + session, err := c.App.DoLogin(c.AppContext, w, r, user, "", isMobile, false, true) + if err != nil { + handleError(err) + return + } + c.AppContext = c.AppContext.WithSession(session) + + auditRec.Success() + c.LogAuditWithUserId(user.Id, "success") + c.App.AttachSessionCookies(c.AppContext, w, r) + if hasRedirectURL { if isMobile { // Mobile clients with redirect url support
server/i18n/en.json+4 −0 modified@@ -4066,6 +4066,10 @@ "id": "api.user.login_ldap.not_available.app_error", "translation": "AD/LDAP not available on this server." }, + { + "id": "api.user.login_with_desktop_token.not_oauth_or_saml_user.app_error", + "translation": "User is not an OAuth or SAML user." + }, { "id": "api.user.oauth_to_email.context.app_error", "translation": "Update password failed because context user_id did not match provided user's id."
server/public/model/client4.go+19 −0 modified@@ -848,6 +848,25 @@ func (c *Client4) login(ctx context.Context, m map[string]string) (*User, *Respo return &user, BuildResponse(r), nil } +func (c *Client4) LoginWithDesktopToken(ctx context.Context, token, deviceId string) (*User, *Response, error) { + m := make(map[string]string) + m["token"] = token + m["deviceId"] = deviceId + r, err := c.DoAPIPost(ctx, "/users/login/desktop_token", MapToJSON(m)) + if err != nil { + return nil, BuildResponse(r), err + } + defer closeBody(r) + c.AuthToken = r.Header.Get(HeaderToken) + c.AuthType = HeaderBearer + + var user User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + return nil, nil, NewAppError("loginWithDesktopToken", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + } + return &user, BuildResponse(r), nil +} + // Logout terminates the current user's session. func (c *Client4) Logout(ctx context.Context) (*Response, error) { r, err := c.DoAPIPost(ctx, "/users/logout", "")
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
4News mentions
0No linked articles in our index yet.