Moderate severityNVD Advisory· Published Apr 5, 2024· Updated Sep 3, 2024
DoS via a large number of User Preferences
CVE-2024-28949
Description
Mattermost Server versions 9.5.x before 9.5.2, 9.4.x before 9.4.4, 9.3.x before 9.3.3, 8.1.x before 8.1.11 don't limit the number of user preferences which allows an attacker to send a large number of user preferences potentially causing denial of service.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/mattermost/mattermost/server/v8Go | >= 8.1.0, < 8.1.11 | 8.1.11 |
github.com/mattermost/mattermost/server/v8Go | >= 9.3.0, < 9.3.3 | 9.3.3 |
github.com/mattermost/mattermost/server/v8Go | >= 9.4.0, < 9.4.4 | 9.4.4 |
github.com/mattermost/mattermost/server/v8Go | >= 9.5.0, < 9.5.2 | 9.5.2 |
Affected products
1- Range: 9.5.0
Patches
45632d6b4ff6dMM 55199 Limit User Preferences (#25579) (#26340)
3 files changed · +90 −4
api/v4/source/preferences.yaml+4 −0 modified@@ -59,6 +59,8 @@ type: array items: $ref: '#/components/schemas/Preference' + minItems: 1 + maxItems: 100 responses: "200": description: User preferences saved successful @@ -102,6 +104,8 @@ type: array items: $ref: '#/components/schemas/Preference' + minItems: 1 + maxItems: 100 responses: "200": description: User preferences saved successful
server/channels/api4/preference.go+14 −4 modified@@ -12,6 +12,8 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/audit" ) +const maxUpdatePreferences = 100 + func (api *API) InitPreference() { api.BaseRoutes.Preferences.Handle("", api.APISessionRequired(getPreferences)).Methods("GET") api.BaseRoutes.Preferences.Handle("", api.APISessionRequired(updatePreferences)).Methods("PUT") @@ -101,8 +103,12 @@ func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) { } var preferences model.Preferences - if jsonErr := json.NewDecoder(r.Body).Decode(&preferences); jsonErr != nil { - c.SetInvalidParamWithErr("preferences", jsonErr) + err := model.StructFromJSONLimited(r.Body, *c.App.Config().ServiceSettings.MaximumPayloadSizeBytes, &preferences) + if err != nil { + c.SetInvalidParamWithErr("preferences", err) + return + } else if len(preferences) == 0 || len(preferences) > maxUpdatePreferences { + c.SetInvalidParam("preferences") return } @@ -149,8 +155,12 @@ func deletePreferences(c *Context, w http.ResponseWriter, r *http.Request) { } var preferences model.Preferences - if jsonErr := json.NewDecoder(r.Body).Decode(&preferences); jsonErr != nil { - c.SetInvalidParamWithErr("preferences", jsonErr) + err := model.StructFromJSONLimited(r.Body, *c.App.Config().ServiceSettings.MaximumPayloadSizeBytes, &preferences) + if err != nil { + c.SetInvalidParamWithErr("preferences", err) + return + } else if len(preferences) == 0 || len(preferences) > maxUpdatePreferences { + c.SetInvalidParam("preferences") return }
server/channels/api4/preference_test.go+72 −0 modified@@ -257,6 +257,42 @@ func TestUpdatePreferences(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestUpdatePreferencesOverload(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + t.Run("No preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + // should error if no preferences + resp, err := client.UpdatePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) + + t.Run("Too many preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + category := model.NewId() + // should error if too many preferences + for i := 0; i <= 100; i++ { + preferences1 = append(preferences1, model.Preference{ + UserId: user1.Id, + Category: category, + Name: model.NewId(), + Value: model.NewId(), + }) + } + resp, err := client.UpdatePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) +} + func TestUpdatePreferencesWebsocket(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() @@ -589,6 +625,42 @@ func TestDeletePreferences(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestDeletePreferencesOverload(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + t.Run("No preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + // should error if no preferences + resp, err := client.DeletePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) + + t.Run("Too many preferences", func(t *testing.T) { + category := model.NewId() + preferences1 := model.Preferences{} + // should error if too many preferences + for i := 0; i <= 100; i++ { + preferences1 = append(preferences1, model.Preference{ + UserId: user1.Id, + Category: category, + Name: model.NewId(), + Value: model.NewId(), + }) + } + resp, err := client.DeletePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) +} + func TestDeletePreferencesWebsocket(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown()
11a21f4da352MM 55199 Limit User Preferences (#25579) (#26339)
3 files changed · +90 −4
api/v4/source/preferences.yaml+4 −0 modified@@ -59,6 +59,8 @@ type: array items: $ref: '#/components/schemas/Preference' + minItems: 1 + maxItems: 100 responses: "200": description: User preferences saved successful @@ -102,6 +104,8 @@ type: array items: $ref: '#/components/schemas/Preference' + minItems: 1 + maxItems: 100 responses: "200": description: User preferences saved successful
server/channels/api4/preference.go+14 −4 modified@@ -12,6 +12,8 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/audit" ) +const maxUpdatePreferences = 100 + func (api *API) InitPreference() { api.BaseRoutes.Preferences.Handle("", api.APISessionRequired(getPreferences)).Methods("GET") api.BaseRoutes.Preferences.Handle("", api.APISessionRequired(updatePreferences)).Methods("PUT") @@ -101,8 +103,12 @@ func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) { } var preferences model.Preferences - if jsonErr := json.NewDecoder(r.Body).Decode(&preferences); jsonErr != nil { - c.SetInvalidParamWithErr("preferences", jsonErr) + err := model.StructFromJSONLimited(r.Body, *c.App.Config().ServiceSettings.MaximumPayloadSizeBytes, &preferences) + if err != nil { + c.SetInvalidParamWithErr("preferences", err) + return + } else if len(preferences) == 0 || len(preferences) > maxUpdatePreferences { + c.SetInvalidParam("preferences") return } @@ -149,8 +155,12 @@ func deletePreferences(c *Context, w http.ResponseWriter, r *http.Request) { } var preferences model.Preferences - if jsonErr := json.NewDecoder(r.Body).Decode(&preferences); jsonErr != nil { - c.SetInvalidParamWithErr("preferences", jsonErr) + err := model.StructFromJSONLimited(r.Body, *c.App.Config().ServiceSettings.MaximumPayloadSizeBytes, &preferences) + if err != nil { + c.SetInvalidParamWithErr("preferences", err) + return + } else if len(preferences) == 0 || len(preferences) > maxUpdatePreferences { + c.SetInvalidParam("preferences") return }
server/channels/api4/preference_test.go+72 −0 modified@@ -257,6 +257,42 @@ func TestUpdatePreferences(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestUpdatePreferencesOverload(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + t.Run("No preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + // should error if no preferences + resp, err := client.UpdatePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) + + t.Run("Too many preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + category := model.NewId() + // should error if too many preferences + for i := 0; i <= 100; i++ { + preferences1 = append(preferences1, model.Preference{ + UserId: user1.Id, + Category: category, + Name: model.NewId(), + Value: model.NewId(), + }) + } + resp, err := client.UpdatePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) +} + func TestUpdatePreferencesWebsocket(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() @@ -589,6 +625,42 @@ func TestDeletePreferences(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestDeletePreferencesOverload(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + t.Run("No preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + // should error if no preferences + resp, err := client.DeletePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) + + t.Run("Too many preferences", func(t *testing.T) { + category := model.NewId() + preferences1 := model.Preferences{} + // should error if too many preferences + for i := 0; i <= 100; i++ { + preferences1 = append(preferences1, model.Preference{ + UserId: user1.Id, + Category: category, + Name: model.NewId(), + Value: model.NewId(), + }) + } + resp, err := client.DeletePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) +} + func TestDeletePreferencesWebsocket(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown()
362b7d29d35cMM 55199 Limit User Preferences (#25579) (#26341)
3 files changed · +90 −4
api/v4/source/preferences.yaml+4 −0 modified@@ -59,6 +59,8 @@ type: array items: $ref: '#/components/schemas/Preference' + minItems: 1 + maxItems: 100 responses: "200": description: User preferences saved successful @@ -102,6 +104,8 @@ type: array items: $ref: '#/components/schemas/Preference' + minItems: 1 + maxItems: 100 responses: "200": description: User preferences saved successful
server/channels/api4/preference.go+14 −4 modified@@ -12,6 +12,8 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/audit" ) +const maxUpdatePreferences = 100 + func (api *API) InitPreference() { api.BaseRoutes.Preferences.Handle("", api.APISessionRequired(getPreferences)).Methods("GET") api.BaseRoutes.Preferences.Handle("", api.APISessionRequired(updatePreferences)).Methods("PUT") @@ -101,8 +103,12 @@ func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) { } var preferences model.Preferences - if jsonErr := json.NewDecoder(r.Body).Decode(&preferences); jsonErr != nil { - c.SetInvalidParamWithErr("preferences", jsonErr) + err := model.StructFromJSONLimited(r.Body, *c.App.Config().ServiceSettings.MaximumPayloadSizeBytes, &preferences) + if err != nil { + c.SetInvalidParamWithErr("preferences", err) + return + } else if len(preferences) == 0 || len(preferences) > maxUpdatePreferences { + c.SetInvalidParam("preferences") return } @@ -149,8 +155,12 @@ func deletePreferences(c *Context, w http.ResponseWriter, r *http.Request) { } var preferences model.Preferences - if jsonErr := json.NewDecoder(r.Body).Decode(&preferences); jsonErr != nil { - c.SetInvalidParamWithErr("preferences", jsonErr) + err := model.StructFromJSONLimited(r.Body, *c.App.Config().ServiceSettings.MaximumPayloadSizeBytes, &preferences) + if err != nil { + c.SetInvalidParamWithErr("preferences", err) + return + } else if len(preferences) == 0 || len(preferences) > maxUpdatePreferences { + c.SetInvalidParam("preferences") return }
server/channels/api4/preference_test.go+72 −0 modified@@ -258,6 +258,42 @@ func TestUpdatePreferences(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestUpdatePreferencesOverload(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + t.Run("No preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + // should error if no preferences + resp, err := client.UpdatePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) + + t.Run("Too many preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + category := model.NewId() + // should error if too many preferences + for i := 0; i <= 100; i++ { + preferences1 = append(preferences1, model.Preference{ + UserId: user1.Id, + Category: category, + Name: model.NewId(), + Value: model.NewId(), + }) + } + resp, err := client.UpdatePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) +} + func TestUpdatePreferencesWebsocket(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() @@ -590,6 +626,42 @@ func TestDeletePreferences(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestDeletePreferencesOverload(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + t.Run("No preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + // should error if no preferences + resp, err := client.DeletePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) + + t.Run("Too many preferences", func(t *testing.T) { + category := model.NewId() + preferences1 := model.Preferences{} + // should error if too many preferences + for i := 0; i <= 100; i++ { + preferences1 = append(preferences1, model.Preference{ + UserId: user1.Id, + Category: category, + Name: model.NewId(), + Value: model.NewId(), + }) + } + resp, err := client.DeletePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) +} + func TestDeletePreferencesWebsocket(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown()
88f9285173dcMM 55199 Limit User Preferences (#25579) (#26336)
3 files changed · +90 −4
api/v4/source/preferences.yaml+4 −0 modified@@ -59,6 +59,8 @@ type: array items: $ref: '#/components/schemas/Preference' + minItems: 1 + maxItems: 100 responses: "200": description: User preferences saved successful @@ -102,6 +104,8 @@ type: array items: $ref: '#/components/schemas/Preference' + minItems: 1 + maxItems: 100 responses: "200": description: User preferences saved successful
server/channels/api4/preference.go+14 −4 modified@@ -12,6 +12,8 @@ import ( "github.com/mattermost/mattermost/server/v8/channels/audit" ) +const maxUpdatePreferences = 100 + func (api *API) InitPreference() { api.BaseRoutes.Preferences.Handle("", api.APISessionRequired(getPreferences)).Methods("GET") api.BaseRoutes.Preferences.Handle("", api.APISessionRequired(updatePreferences)).Methods("PUT") @@ -101,8 +103,12 @@ func updatePreferences(c *Context, w http.ResponseWriter, r *http.Request) { } var preferences model.Preferences - if jsonErr := json.NewDecoder(r.Body).Decode(&preferences); jsonErr != nil { - c.SetInvalidParamWithErr("preferences", jsonErr) + err := model.StructFromJSONLimited(r.Body, *c.App.Config().ServiceSettings.MaximumPayloadSizeBytes, &preferences) + if err != nil { + c.SetInvalidParamWithErr("preferences", err) + return + } else if len(preferences) == 0 || len(preferences) > maxUpdatePreferences { + c.SetInvalidParam("preferences") return } @@ -149,8 +155,12 @@ func deletePreferences(c *Context, w http.ResponseWriter, r *http.Request) { } var preferences model.Preferences - if jsonErr := json.NewDecoder(r.Body).Decode(&preferences); jsonErr != nil { - c.SetInvalidParamWithErr("preferences", jsonErr) + err := model.StructFromJSONLimited(r.Body, *c.App.Config().ServiceSettings.MaximumPayloadSizeBytes, &preferences) + if err != nil { + c.SetInvalidParamWithErr("preferences", err) + return + } else if len(preferences) == 0 || len(preferences) > maxUpdatePreferences { + c.SetInvalidParam("preferences") return }
server/channels/api4/preference_test.go+72 −0 modified@@ -257,6 +257,42 @@ func TestUpdatePreferences(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestUpdatePreferencesOverload(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + t.Run("No preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + // should error if no preferences + resp, err := client.UpdatePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) + + t.Run("Too many preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + category := model.NewId() + // should error if too many preferences + for i := 0; i <= 100; i++ { + preferences1 = append(preferences1, model.Preference{ + UserId: user1.Id, + Category: category, + Name: model.NewId(), + Value: model.NewId(), + }) + } + resp, err := client.UpdatePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) +} + func TestUpdatePreferencesWebsocket(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() @@ -589,6 +625,42 @@ func TestDeletePreferences(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestDeletePreferencesOverload(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + client := th.Client + + th.LoginBasic() + user1 := th.BasicUser + + t.Run("No preferences", func(t *testing.T) { + preferences1 := model.Preferences{} + // should error if no preferences + resp, err := client.DeletePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) + + t.Run("Too many preferences", func(t *testing.T) { + category := model.NewId() + preferences1 := model.Preferences{} + // should error if too many preferences + for i := 0; i <= 100; i++ { + preferences1 = append(preferences1, model.Preference{ + UserId: user1.Id, + Category: category, + Name: model.NewId(), + Value: model.NewId(), + }) + } + resp, err := client.DeletePreferences(context.Background(), user1.Id, preferences1) + require.Error(t, err) + CheckErrorID(t, err, "api.context.invalid_body_param.app_error") + CheckBadRequestStatus(t, resp) + }) +} + func TestDeletePreferencesWebsocket(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown()
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
9- github.com/advisories/GHSA-mcw6-3256-64ggghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-28949ghsaADVISORY
- github.com/mattermost/mattermost/commit/11a21f4da352a472a09de3b8e125514750a6619aghsaWEB
- github.com/mattermost/mattermost/commit/362b7d29d35c00fe80721d3d47442a4f3168eb2bghsaWEB
- github.com/mattermost/mattermost/commit/5632d6b4ff6d019a21bb8ddd037d4a931cd85ae2ghsaWEB
- github.com/mattermost/mattermost/commit/88f9285173dc4cb35fa19a8b8604e098a567f704ghsaWEB
- mattermost.com/security-updatesghsaWEB
- pkg.go.dev/vuln/GO-2024-2695ghsaWEB
- mattermost/mattermostghsaPACKAGE
News mentions
0No linked articles in our index yet.