VYPR
Medium severity4.3NVD Advisory· Published May 18, 2026· Updated May 18, 2026

CVE-2026-6343

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

Patches

1
aa53aca3487f

oauth check (#35553) (#35684)

https://github.com/mattermost/mattermostMattermost BuildMar 19, 2026Fixed in 11.5.2via llm-release-walk
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

News mentions

0

No linked articles in our index yet.