VYPR
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.

PackageAffected versionsPatched versions
github.com/mattermost/mattermost/server/v8Go
< 8.0.0-20240821220019-0d6b1070a26f8.0.0-20240821220019-0d6b1070a26f

Affected products

1

Patches

1
0d6b1070a26f

[MM-58549] Desktop login (#27732)

https://github.com/mattermost/mattermostBen CookeAug 21, 2024via ghsa
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

4

News mentions

0

No linked articles in our index yet.