CVE-2026-6334
Description
Mattermost versions 11.5.x <= 11.5.1, 10.11.x <= 10.11.13 fail to enforce client identity binding during the OAuth authorization code redemption flow which allows an authenticated OAuth client to redeem authorization codes issued to a different client via a crafted token exchange request.. Mattermost Advisory ID: MMSA-2026-00570
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Mattermost fails to enforce client identity binding during OAuth authorization code redemption, allowing authenticated OAuth clients to redeem codes issued to other clients.
Vulnerability
Mattermost versions 11.5.x up to and including 11.5.1, and 10.11.x up to and including 10.11.13, do not enforce client identity binding during the OAuth authorization code redemption flow [1]. This means that when a legitimate OAuth client obtains an authorization code, an authenticated but different OAuth client can exchange that code for tokens by crafting a token exchange request without the correct client_id validation.
Exploitation
An attacker must be an authenticated OAuth client within the Mattermost instance (i.e., have valid client credentials). The attacker intercepts or obtains an authorization code that was issued to another client (e.g., via a phishing or misconfiguration) and then sends a crafted token exchange request to the Mattermost server, substituting their own client credentials but using the victim's authorization code. The server fails to verify that the client redeeming the code matches the client to which the code was originally issued.
Impact
An attacker can redeem authorization codes intended for other OAuth clients, potentially gaining access tokens that grant privileges or access scopes associated with the victim client. The impact is limited to authentication bypass in OAuth flows; the attacker does not gain direct system access beyond what the victim client is authorized for.
Mitigation
According to Mattermost Security Advisory MMSA-2026-00570 [1], no specific fixed version is provided in the public advisory. Users should monitor the Mattermost security updates page for the release of a patched version. As a workaround, restrict OAuth client registration and monitor token exchange requests.
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.0 <= 11.5.1, >= 10.11.0 <= 10.11.13
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 client identity binding check during OAuth authorization code and refresh token redemption allows one authenticated client to redeem tokens issued to a different client."
Attack vector
An attacker who controls an authenticated OAuth client (client B) can craft a token exchange request using an authorization code or refresh token that was issued to a different client (client A). The request must include client B's own client ID and client secret, but supply the authorization code or refresh token that belongs to client A. Before the patch, the server did not verify that the client ID in the token request matched the client ID stored with the authorization code or refresh token [CWE-305]. This attack requires the attacker to first obtain a valid authorization code or refresh token intended for another client, and to have valid credentials for their own OAuth client.
Affected code
The vulnerability exists in `server/channels/app/oauth.go` in two functions: `handleAuthorizationCodeGrant` (around line 341) and `handleRefreshTokenGrant` (around line 395). Both functions lacked a check comparing the client ID associated with the stored authorization data against the client ID presented in the token exchange request.
What the fix does
The patch adds two identity-binding checks in `server/channels/app/oauth.go`. In `handleAuthorizationCodeGrant`, a new comparison verifies that `authData.ClientId` (the client ID stored with the authorization code) matches the `clientId` parameter from the incoming request [patch_id=918568]. Similarly, in `handleRefreshTokenGrant`, the patch checks that `accessData.ClientId` (the client ID stored with the refresh token) matches `oauthApp.Id`. If either check fails, the function returns a new `client_id_mismatch` error. These checks ensure that only the OAuth client to which a code or token was originally issued can redeem it, closing the authentication bypass.
Preconditions
- authAttacker must have valid credentials (client ID and client secret) for an authenticated OAuth client (client B).
- inputAttacker must obtain a valid authorization code or refresh token that was issued to a different OAuth client (client A).
- networkThe attacker must be able to send crafted HTTP requests to the Mattermost OAuth token endpoint.
Generated on May 20, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1News mentions
0No linked articles in our index yet.