CVE-2026-6343
Description
Mattermost versions 11.5.x <= 11.5.1, 10.11.x <= 10.11.13, 11.4.x <= 11.4.3 fail to check public/private permissions which allows members without these permissions to access public playbooks via /get.. Mattermost Advisory ID: MMSA-2026-00591
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Mattermost fails to check public/private permissions, allowing members without them to access public playbooks via /get.
Vulnerability
Mattermost versions 11.5.x <= 11.5.1, 10.11.x <= 10.11.13, and 11.4.x <= 11.4.3 fail to properly enforce public/private permission checks when accessing playbooks. This allows members who lack explicit permissions to view or interact with public playbooks via the /get endpoint. The issue is tracked as MMSA-2026-00591 [1].
Exploitation
An authenticated attacker with membership in a workspace but without the required permissions for playbook access can send a crafted request to the /get endpoint. No additional privileges or user interaction are needed beyond normal authentication.
Impact
Successful exploitation results in unauthorized access to public playbooks, potentially exposing sensitive operational data or workflows. The confidentiality of playbook information is breached, though write or administrative access is not gained.
Mitigation
Mattermost has released fixes in versions 11.5.2, 10.11.14, and 11.4.4. Users should upgrade to these versions or later. No workarounds are documented. The advisory is available on the Mattermost security updates page [1].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1- Range: 11.5.x <= 11.5.1, 10.11.x <= 10.11.13, 11.4.x <= 11.4.3
Patches
1aa53aca3487foauth check (#35553) (#35684)
3 files changed · +70 −0
server/channels/app/oauth.go+8 −0 modified@@ -341,6 +341,10 @@ func (a *App) handleAuthorizationCodeGrant(rctx request.CTX, oauthApp *model.OAu return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusForbidden) } + if authData.ClientId != clientId { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.client_id_mismatch.app_error", nil, "", http.StatusBadRequest) + } + if authData.RedirectUri != redirectURI { return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.redirect_uri.app_error", nil, "", http.StatusBadRequest) } @@ -395,6 +399,10 @@ func (a *App) handleRefreshTokenGrant(rctx request.CTX, oauthApp *model.OAuthApp return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.refresh_token.app_error", nil, "", http.StatusNotFound).Wrap(nErr) } + if accessData.ClientId != oauthApp.Id { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.client_id_mismatch.app_error", nil, "", http.StatusBadRequest) + } + user, nErr := a.Srv().Store().User().Get(context.Background(), accessData.UserId) if nErr != nil { return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "", http.StatusNotFound).Wrap(nErr)
server/channels/app/oauth_test.go+58 −0 modified@@ -1400,6 +1400,64 @@ func TestGetOAuthAccessTokenForCodeFlow(t *testing.T) { require.Contains(t, appErr.Id, "resource_mismatch") }) }) + + t.Run("DifferentClient_CannotRedeemCode", func(t *testing.T) { + appA := createConfidentialOAuthApp("TestClientA") + appB := createConfidentialOAuthApp("TestClientB") + code := getAuthorizationCode(appA, "") + + _, appErr := th.App.GetOAuthAccessTokenForCodeFlow( + th.Context, + appB.Id, + model.AccessTokenGrantType, + appA.CallbackUrls[0], + code, + appB.ClientSecret, + "", + "", + "", + ) + require.NotNil(t, appErr) + require.Contains(t, appErr.Id, "client_id_mismatch") + require.Equal(t, http.StatusBadRequest, appErr.StatusCode) + }) + + t.Run("DifferentClient_CannotUseRefreshToken", func(t *testing.T) { + appA := createConfidentialOAuthApp("TestClientA") + appB := createConfidentialOAuthApp("TestClientB") + code := getAuthorizationCode(appA, "") + + // Get a valid refresh token for appA + tokenResp, appErr := th.App.GetOAuthAccessTokenForCodeFlow( + th.Context, + appA.Id, + model.AccessTokenGrantType, + appA.CallbackUrls[0], + code, + appA.ClientSecret, + "", + "", + "", + ) + require.Nil(t, appErr) + require.NotEmpty(t, tokenResp.RefreshToken) + + // Try to use appA's refresh token with appB's credentials + _, appErr = th.App.GetOAuthAccessTokenForCodeFlow( + th.Context, + appB.Id, + model.RefreshTokenGrantType, + appB.CallbackUrls[0], + "", + appB.ClientSecret, + tokenResp.RefreshToken, + "", + "", + ) + require.NotNil(t, appErr) + require.Contains(t, appErr.Id, "client_id_mismatch") + require.Equal(t, http.StatusBadRequest, appErr.StatusCode) + }) } func TestParseOAuthStateTokenExtra(t *testing.T) { t.Run("valid token with normal values", func(t *testing.T) {
server/i18n/en.json+4 −0 modified@@ -2576,6 +2576,10 @@ "id": "api.oauth.get_access_token.bad_request.app_error", "translation": "invalid_request: Bad request." }, + { + "id": "api.oauth.get_access_token.client_id_mismatch.app_error", + "translation": "invalid_grant: Token grant was not issued to this client." + }, { "id": "api.oauth.get_access_token.credentials.app_error", "translation": "invalid_client: Invalid client credentials."
Vulnerability mechanics
Root cause
"Missing authorization check allows an OAuth client to redeem an authorization code or refresh token that was issued to a different client."
Attack vector
An attacker who controls a malicious OAuth application (client B) can intercept an authorization code or refresh token that was issued to a legitimate OAuth application (client A). By calling the token endpoint with client B's credentials but using client A's code or refresh token, the attacker can obtain an access token for the victim user's session. The vulnerability exists because the server previously did not verify that the client ID in the stored authorization data matches the client ID presented in the token request [CWE-863]. The attack requires network access and a low-privilege account, but no special permissions beyond being able to register or use an OAuth app.
Affected code
The vulnerability is in `server/channels/app/oauth.go` within the `handleAuthorizationCodeGrant` and `handleRefreshTokenGrant` functions. Both functions lacked a check that the client ID stored in the authorization data matches the client ID from the incoming token request.
What the fix does
The patch adds a client ID mismatch check in two OAuth grant handlers. In `handleAuthorizationCodeGrant` [patch_id=918507], the code now compares `authData.ClientId` (the client that the code was issued to) against the `clientId` parameter from the request. In `handleRefreshTokenGrant` [patch_id=918507], it compares `accessData.ClientId` against `oauthApp.Id`. If the IDs do not match, the function returns a new `client_id_mismatch` error and rejects the request. This closes the authorization bypass by ensuring that a token grant can only be redeemed by the same OAuth client that originally obtained it.
Preconditions
- authAttacker must have a low-privilege account on the Mattermost instance.
- configThe instance must have OAuth 2.0 applications enabled and the attacker must be able to register or control a malicious OAuth client.
- networkAttacker must have network access to the Mattermost API endpoint for token exchange.
- inputAttacker must obtain a valid authorization code or refresh token that was issued to a different OAuth client.
Generated on May 20, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1- mattermost.com/security-updatesnvdVendor Advisory
News mentions
0No linked articles in our index yet.