CVE-2017-18878
Description
An issue was discovered in Mattermost Server before 4.3.0, 4.2.1, and 4.1.2. Knowledge of a session ID allows revoking another user's session.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Mattermost Server before 4.3.0/4.2.1/4.1.2 allows any user to revoke another user's session if the session ID is known, due to insufficient authorization checks.
Vulnerability
Description
Mattermost Server versions prior to 4.3.0, 4.2.1, and 4.1.2 contain an authorization vulnerability in the session revocation endpoint. The flaw occurs because the server does not verify that the session being revoked belongs to the authenticated user making the request. As a result, an attacker who obtains a valid session ID can revoke that session, effectively logging out the victim [1].
Exploitation
Conditions
Exploitation requires knowledge of a target user's session ID. While session IDs are typically not disclosed, they could be obtained through other means such as cross-site scripting, network sniffing, or access to server logs. No special privileges are needed beyond a valid user account to make the revocation request. The attack can be performed over the network without authentication for the CVSS scoring, but practical exploitation often requires some initial access or information leakage.
Impact
A successful attack forces a user's session to be terminated, causing denial of service for that user. Repeated session revocation could prevent normal use of Mattermost. The vulnerability does not allow data theft or privilege escalation, but it disrupts availability and may facilitate further attacks if combined with other weaknesses.
Mitigation
The issue is fixed in Mattermost Server versions 4.3.0, 4.2.1, and 4.1.2. The fix ensures that the session ID submitted for revocation belongs to the current user, preventing unauthorized revocation [4]. Administrators should update to these or later versions. No known workarounds exist; upgrading is the recommended remediation.
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 packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/mattermost/mattermost-serverGo | < 4.1.2-0.20171004201910-6be8113eb60c | 4.1.2-0.20171004201910-6be8113eb60c |
github.com/mattermost/mattermost-serverGo | >= 4.2.0-rc1, < 4.2.1-0.20171004192657-8fbbd688ea24 | 4.2.1-0.20171004192657-8fbbd688ea24 |
github.com/mattermost/mattermost-serverGo | >= 4.3.0-rc1, < 4.3.0 | 4.3.0 |
Affected products
3- Mattermost/Serverdescription
- ghsa-coords2 versionspkg:golang/github.com/mattermost/mattermost-serverpkg:rpm/opensuse/govulncheck-vulndb&distro=openSUSE%20Leap%2015.6
< 4.1.2-0.20171004201910-6be8113eb60c+ 1 more
- (no CPE)range: < 4.1.2-0.20171004201910-6be8113eb60c
- (no CPE)range: < 0.0.20251230T014957-150000.1.134.1
Patches
314 files changed · +229 −60
api4/channel_test.go+1 −1 modified@@ -1473,7 +1473,7 @@ func TestGetChannelUnread(t *testing.T) { CheckNoError(t, resp) _, resp = th.SystemAdminClient.GetChannelUnread(model.NewId(), user.Id) - CheckNotFoundStatus(t, resp) + CheckForbiddenStatus(t, resp) _, resp = th.SystemAdminClient.GetChannelUnread(channel.Id, model.NewId()) CheckNotFoundStatus(t, resp)
api4/command_test.go+3 −6 modified@@ -470,30 +470,27 @@ func TestExecuteCommand(t *testing.T) { func TestExecuteCommandAgainstChannelOnAnotherTeam(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() + defer TearDown() Client := th.Client channel := th.BasicChannel enableCommands := *utils.Cfg.ServiceSettings.EnableCommands - allowedInternalConnections := *utils.Cfg.ServiceSettings.AllowedUntrustedInternalConnections defer func() { utils.Cfg.ServiceSettings.EnableCommands = &enableCommands - utils.Cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections }() *utils.Cfg.ServiceSettings.EnableCommands = true - *utils.Cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost" // create a slash command on some other team where we have permission to do so team2 := th.CreateTeam() postCmd := &model.Command{ CreatorId: th.BasicUser.Id, TeamId: team2.Id, - URL: "http://localhost" + *utils.Cfg.ServiceSettings.ListenAddress + model.API_URL_SUFFIX_V4 + "/teams/command_test", + URL: "http://localhost" + utils.Cfg.ServiceSettings.ListenAddress + model.API_URL_SUFFIX_V4 + "/teams/command_test", Method: model.COMMAND_METHOD_POST, Trigger: "postcommand", } - if _, err := th.App.CreateCommand(postCmd); err != nil { + if _, err := app.CreateCommand(postCmd); err != nil { t.Fatal("failed to create post command") }
api4/file_test.go+9 −0 modified@@ -100,6 +100,15 @@ func TestUploadFile(t *testing.T) { _, resp := Client.UploadFile(data, model.NewId(), "test.png") CheckForbiddenStatus(t, resp) + _, resp = Client.UploadFile(data, "../../junk", "test.png") + CheckForbiddenStatus(t, resp) + + _, resp = th.SystemAdminClient.UploadFile(data, model.NewId(), "test.png") + CheckForbiddenStatus(t, resp) + + _, resp = th.SystemAdminClient.UploadFile(data, "../../junk", "test.png") + CheckForbiddenStatus(t, resp) + _, resp = th.SystemAdminClient.UploadFile(data, channel.Id, "test.png") CheckNoError(t, resp)
api4/user.go+59 −1 modified@@ -539,6 +539,20 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { return } + if c.Session.IsOAuth { + ouser, err := app.GetUser(user.Id) + if err != nil { + c.Err = err + return + } + + if ouser.Email != user.Email { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + c.Err.DetailedError += ", attempted email update by oauth app" + return + } + } + if ruser, err := app.UpdateUserAsUser(user, c.IsSystemAdmin()); err != nil { c.Err = err return @@ -565,6 +579,20 @@ func patchUser(c *Context, w http.ResponseWriter, r *http.Request) { return } + if c.Session.IsOAuth && patch.Email != nil { + ouser, err := app.GetUser(c.Params.UserId) + if err != nil { + c.Err = err + return + } + + if ouser.Email != *patch.Email { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + c.Err.DetailedError += ", attempted email update by oauth app" + return + } + } + if ruser, err := app.PatchUser(c.Params.UserId, patch, c.IsSystemAdmin()); err != nil { c.Err = err return @@ -693,6 +721,12 @@ func updateUserMfa(c *Context, w http.ResponseWriter, r *http.Request) { return } + if c.Session.IsOAuth { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + c.Err.DetailedError += ", attempted access by oauth app" + return + } + if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) return @@ -732,6 +766,12 @@ func generateMfaSecret(c *Context, w http.ResponseWriter, r *http.Request) { return } + if c.Session.IsOAuth { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + c.Err.DetailedError += ", attempted access by oauth app" + return + } + if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) return @@ -928,7 +968,19 @@ func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { c.SetInvalidParam("session_id") } - if err := app.RevokeSessionById(sessionId); err != nil { + var session *model.Session + var err *model.AppError + if session, err = app.GetSessionById(sessionId); err != nil { + c.Err = err + return + } + + if session.UserId != c.Params.UserId { + c.SetInvalidUrlParam("user_id") + return + } + + if err := app.RevokeSession(session); err != nil { c.Err = err return } @@ -1093,6 +1145,12 @@ func createUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { return } + if c.Session.IsOAuth { + c.SetPermissionError(model.PERMISSION_CREATE_USER_ACCESS_TOKEN) + c.Err.DetailedError += ", attempted access by oauth app" + return + } + accessToken := model.UserAccessTokenFromJson(r.Body) if accessToken == nil { c.SetInvalidParam("user_access_token")
api4/user_test.go+89 −28 modified@@ -994,6 +994,15 @@ func TestUpdateUser(t *testing.T) { } } + session, _ := app.GetSession(Client.AuthToken) + session.IsOAuth = true + app.AddSessionToCache(session) + + ruser.Id = user.Id + ruser.Email = GenerateTestEmail() + _, resp = Client.UpdateUser(ruser) + CheckForbiddenStatus(t, resp) + Client.Logout() _, resp = Client.UpdateUser(user) CheckUnauthorizedStatus(t, resp) @@ -1073,6 +1082,15 @@ func TestPatchUser(t *testing.T) { } } + session, _ := app.GetSession(Client.AuthToken) + session.IsOAuth = true + app.AddSessionToCache(session) + + patch.Email = new(string) + *patch.Email = GenerateTestEmail() + _, resp = Client.PatchUser(user.Id, patch) + CheckForbiddenStatus(t, resp) + Client.Logout() _, resp = Client.PatchUser(user.Id, patch) CheckUnauthorizedStatus(t, resp) @@ -1508,7 +1526,7 @@ func TestGetUsersNotInChannel(t *testing.T) { CheckNoError(t, resp) } -/*func TestUpdateUserMfa(t *testing.T) { +func TestUpdateUserMfa(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer TearDown() Client := th.Client @@ -1518,37 +1536,42 @@ func TestGetUsersNotInChannel(t *testing.T) { enableMfa := *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication defer func() { utils.IsLicensed = isLicensed - utils.License = license + utils.SetLicense(license) *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa }() utils.IsLicensed = true - utils.License = &model.License{Features: &model.Features{}} + utils.SetLicense(&model.License{Features: &model.Features{}}) utils.License.Features.SetDefaults() + *utils.License.Features.MFA = true + *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = true - team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - rteam, _ := Client.CreateTeam(&team) - - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} - ruser, _ := Client.CreateUser(&user) - LinkUserToTeam(ruser, rteam) - store.Must(app.Srv.Store.User().VerifyEmail(ruser.Id)) - - Client.Logout() - _, resp := Client.UpdateUserMfa(ruser.Id, "12334", true) - CheckUnauthorizedStatus(t, resp) - - Client.Login(user.Email, user.Password) - _, resp = Client.UpdateUserMfa("fail", "56789", false) - CheckBadRequestStatus(t, resp) - - _, resp = Client.UpdateUserMfa(ruser.Id, "", true) - CheckErrorMessage(t, resp, "api.context.invalid_body_param.app_error") + session, _ := app.GetSession(Client.AuthToken) + session.IsOAuth = true + app.AddSessionToCache(session) - *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = true + _, resp := Client.UpdateUserMfa(th.BasicUser.Id, "12345", false) + CheckForbiddenStatus(t, resp) - _, resp = Client.UpdateUserMfa(ruser.Id, "123456", false) - CheckNotImplementedStatus(t, resp) -}*/ + /* + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + rteam, _ := Client.CreateTeam(&team) + user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} + ruser, _ := Client.CreateUser(&user) + th.LinkUserToTeam(ruser, rteam) + store.Must(app.Srv.Store.User().VerifyEmail(ruser.Id)) + Client.Logout() + _, resp := Client.UpdateUserMfa(ruser.Id, "12334", true) + CheckUnauthorizedStatus(t, resp) + Client.Login(user.Email, user.Password) + _, resp = Client.UpdateUserMfa("fail", "56789", false) + CheckBadRequestStatus(t, resp) + _, resp = Client.UpdateUserMfa(ruser.Id, "", true) + CheckErrorMessage(t, resp, "api.context.invalid_body_param.app_error") + *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = true + _, resp = Client.UpdateUserMfa(ruser.Id, "123456", false) + CheckNotImplementedStatus(t, resp) + */ +} func TestCheckUserMfa(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() @@ -1615,19 +1638,40 @@ func TestGenerateMfaSecret(t *testing.T) { _, resp := Client.GenerateMfaSecret(th.BasicUser.Id) CheckNotImplementedStatus(t, resp) + _, resp = th.SystemAdminClient.GenerateMfaSecret(th.BasicUser.Id) + CheckNotImplementedStatus(t, resp) + _, resp = Client.GenerateMfaSecret("junk") CheckBadRequestStatus(t, resp) + isLicensed := utils.IsLicensed + license := utils.License + enableMfa := *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication + defer func() { + utils.IsLicensed = isLicensed + utils.SetLicense(license) + *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa + }() + utils.IsLicensed = true + utils.SetLicense(&model.License{Features: &model.Features{}}) + utils.License.Features.SetDefaults() + *utils.License.Features.MFA = true + *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = true + _, resp = Client.GenerateMfaSecret(model.NewId()) CheckForbiddenStatus(t, resp) + session, _ := app.GetSession(Client.AuthToken) + session.IsOAuth = true + app.AddSessionToCache(session) + + _, resp = Client.GenerateMfaSecret(th.BasicUser.Id) + CheckForbiddenStatus(t, resp) + Client.Logout() _, resp = Client.GenerateMfaSecret(th.BasicUser.Id) CheckUnauthorizedStatus(t, resp) - - _, resp = th.SystemAdminClient.GenerateMfaSecret(th.BasicUser.Id) - CheckNotImplementedStatus(t, resp) } func TestUpdateUserPassword(t *testing.T) { @@ -1880,6 +1924,14 @@ func TestRevokeSessions(t *testing.T) { } CheckNoError(t, resp) + th.LoginBasic() + + sessions, _ = app.GetSessions(th.SystemAdminUser.Id) + session = sessions[0] + + _, resp = Client.RevokeSession(user.Id, session.Id) + CheckBadRequestStatus(t, resp) + Client.Logout() _, resp = Client.RevokeSession(user.Id, model.NewId()) CheckUnauthorizedStatus(t, resp) @@ -2212,6 +2264,15 @@ func TestCreateUserAccessToken(t *testing.T) { if ruser.Id != th.BasicUser.Id { t.Fatal("returned wrong user") } + + Client.AuthToken = oldSessionToken + + session, _ := app.GetSession(Client.AuthToken) + session.IsOAuth = true + app.AddSessionToCache(session) + + _, resp = Client.CreateUserAccessToken(th.BasicUser.Id, testDescription) + CheckForbiddenStatus(t, resp) } func TestGetUserAccessToken(t *testing.T) {
api4/webhook_test.go+1 −5 modified@@ -387,11 +387,7 @@ func TestGetOutgoingWebhooks(t *testing.T) { } hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForChannel(model.NewId(), 0, 1000, "") - CheckNoError(t, resp) - - if len(hooks) != 0 { - t.Fatal("no hooks should be returned") - } + CheckForbiddenStatus(t, resp) _, resp = Client.GetOutgoingWebhooks(0, 1000, "") CheckForbiddenStatus(t, resp)
api/file_test.go+20 −2 modified@@ -24,7 +24,7 @@ import ( ) func TestUploadFile(t *testing.T) { - th := Setup().InitBasic() + th := Setup().InitBasic().InitSystemAdmin() if utils.Cfg.FileSettings.DriverName == "" { t.Logf("skipping because no file driver is enabled") @@ -37,7 +37,9 @@ func TestUploadFile(t *testing.T) { channel := th.BasicChannel var uploadInfo *model.FileInfo - if data, err := readTestFile("test.png"); err != nil { + var data []byte + var err error + if data, err = readTestFile("test.png"); err != nil { t.Fatal(err) } else if resp, err := Client.UploadPostAttachment(data, channel.Id, "test.png"); err != nil { t.Fatal(err) @@ -100,6 +102,22 @@ func TestUploadFile(t *testing.T) { t.Fatalf("file preview should've been saved in %v", expectedPreviewPath) } + if _, err := Client.UploadPostAttachment(data, model.NewId(), "test.png"); err == nil || err.StatusCode != http.StatusForbidden { + t.Fatal("should have failed - bad channel id") + } + + if _, err := Client.UploadPostAttachment(data, "../../junk", "test.png"); err == nil || err.StatusCode != http.StatusForbidden { + t.Fatal("should have failed - bad channel id") + } + + if _, err := th.SystemAdminClient.UploadPostAttachment(data, model.NewId(), "test.png"); err == nil || err.StatusCode != http.StatusForbidden { + t.Fatal("should have failed - bad channel id") + } + + if _, err := th.SystemAdminClient.UploadPostAttachment(data, "../../junk", "test.png"); err == nil || err.StatusCode != http.StatusForbidden { + t.Fatal("should have failed - bad channel id") + } + enableFileAttachments := *utils.Cfg.FileSettings.EnableFileAttachments defer func() { *utils.Cfg.FileSettings.EnableFileAttachments = enableFileAttachments
api/oauth_test.go+0 −8 modified@@ -719,14 +719,6 @@ func TestOAuthAccessToken(t *testing.T) { t.Fatal("Should have failed - code is expired") } - authData = &model.AuthData{ClientId: oauthApp.Id, RedirectUri: oauthApp.CallbackUrls[0], UserId: th.BasicUser.Id, Code: model.NewId(), ExpiresIn: model.AUTHCODE_EXPIRE_TIME} - <-app.Srv.Store.OAuth().SaveAuthData(authData) - - data.Set("code", authData.Code) - if _, err := Client.GetAccessToken(data); err == nil { - t.Fatal("Should have failed - code with invalid hash comparission") - } - Client.ClearOAuthToken() }
app/authorization.go+3 −0 modified@@ -4,6 +4,7 @@ package app import ( + "net/http" "strings" l4g "github.com/alecthomas/log4go" @@ -50,6 +51,8 @@ func SessionHasPermissionToChannel(session model.Session, channelId string, perm channel, err := GetChannel(channelId) if err == nil && channel.TeamId != "" { return SessionHasPermissionToTeam(session, channel.TeamId, permission) + } else if err != nil && err.StatusCode == http.StatusNotFound { + return false } return SessionHasPermissionTo(session, permission)
app/file.go+4 −1 modified@@ -462,8 +462,11 @@ func UploadFiles(teamId string, channelId string, userId string, fileHeaders []* return resStruct, nil } -func DoUploadFile(teamId string, channelId string, userId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) { +func DoUploadFile(rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) { filename := filepath.Base(rawFilename) + teamId := filepath.Base(rawTeamId) + channelId := filepath.Base(rawChannelId) + userId := filepath.Base(rawUserId) info, err := model.GetInfoForBytes(filename, data) if err != nil {
app/file_test.go+17 −0 modified@@ -4,9 +4,12 @@ package app import ( + "fmt" "testing" + "time" "github.com/mattermost/platform/model" + "github.com/mattermost/platform1/platform/utils" ) func TestGeneratePublicLinkHash(t *testing.T) { @@ -30,4 +33,18 @@ func TestGeneratePublicLinkHash(t *testing.T) { if hash1 == hash3 { t.Fatal("hashes for the same file with different salts should not be equal") } + + info4, err := DoUploadFile(time.Date(2009, 3, 5, 1, 2, 3, 4, time.Local), "../../"+teamId, "../../"+channelId, "../../"+userId, "../../"+filename, data) + if err != nil { + t.Fatal(err) + } else { + defer func() { + <-Srv.Store.FileInfo().PermanentDelete(info3.Id) + utils.RemoveFile(info3.Path) + }() + } + + if info4.Path != fmt.Sprintf("20090305/teams/%v/channels/%v/users/%v/%v/%v", teamId, channelId, userId, info4.Id, filename) { + t.Fatal("stored file at incorrect path", info4.Path) + } }
app/oauth.go+1 −6 modified@@ -6,7 +6,6 @@ package app import ( "bytes" b64 "encoding/base64" - "fmt" "io" "io/ioutil" "net/http" @@ -116,7 +115,7 @@ func AllowOAuthAppAccessToUser(userId string, authRequest *model.AuthorizeReques } authData := &model.AuthData{UserId: userId, ClientId: authRequest.ClientId, CreateAt: model.GetMillis(), RedirectUri: authRequest.RedirectUri, State: authRequest.State, Scope: authRequest.Scope} - authData.Code = utils.HashSha256(fmt.Sprintf("%v:%v:%v:%v", authRequest.ClientId, authRequest.RedirectUri, authData.CreateAt, userId)) + authData.Code = model.NewId() + model.NewId() // this saves the OAuth2 app as authorized authorizedApp := model.Preference{ @@ -174,10 +173,6 @@ func GetOAuthAccessToken(clientId, grantType, redirectUri, code, secret, refresh return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.redirect_uri.app_error", nil, "", http.StatusBadRequest) } - if code != utils.HashSha256(fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) { - return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusBadRequest) - } - if result := <-Srv.Store.User().Get(authData.UserId); result.Err != nil { return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "", http.StatusNotFound) } else {
app/session.go+9 −0 modified@@ -164,6 +164,15 @@ func RevokeSessionsForDeviceId(userId string, deviceId string, currentSessionId return nil } +func GetSessionById(sessionId string) (*model.Session, *model.AppError) { + if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil { + result.Err.StatusCode = http.StatusBadRequest + return nil, result.Err + } else { + return result.Data.(*model.Session), nil + } +} + func RevokeSessionById(sessionId string) *model.AppError { if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil { result.Err.StatusCode = http.StatusBadRequest
webapp/components/authorize.jsx+13 −2 modified@@ -29,8 +29,13 @@ export default class Authorize extends React.Component { } componentWillMount() { + const clientId = this.props.location.query.client_id; + if (!(/^[a-z0-9]+$/.test(clientId))) { + return; + } + getOAuthAppInfo( - this.props.location.query.client_id, + clientId, (app) => { this.setState({app}); } @@ -61,7 +66,13 @@ export default class Authorize extends React.Component { } handleDeny() { - window.location.replace(this.props.location.query.redirect_uri + '?error=access_denied'); + const redirectUri = this.props.location.query.redirect_uri; + if (redirectUri.startsWith('https://') || redirectUri.startsWith('http://')) { + window.location.replace(redirectUri + '?error=access_denied'); + return; + } + + window.location.replace('/error'); } render() {
8fbbd688ea24Updates to session revoking in v4
4 files changed · +32 −3
api4/command_test.go+2 −2 modified@@ -486,7 +486,7 @@ func TestExecuteCommand(t *testing.T) { func TestExecuteCommandAgainstChannelOnAnotherTeam(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() - defer th.TearDown() + defer TearDown() Client := th.Client channel := th.BasicChannel @@ -509,7 +509,7 @@ func TestExecuteCommandAgainstChannelOnAnotherTeam(t *testing.T) { Trigger: "postcommand", } - if _, err := th.App.CreateCommand(postCmd); err != nil { + if _, err := app.CreateCommand(postCmd); err != nil { t.Fatal("failed to create post command") }
api4/user.go+13 −1 modified@@ -924,7 +924,19 @@ func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { return } - if err := app.RevokeSessionById(sessionId); err != nil { + var session *model.Session + var err *model.AppError + if session, err = app.GetSessionById(sessionId); err != nil { + c.Err = err + return + } + + if session.UserId != c.Params.UserId { + c.SetInvalidUrlParam("user_id") + return + } + + if err := app.RevokeSession(session); err != nil { c.Err = err return }
api4/user_test.go+8 −0 modified@@ -1882,6 +1882,14 @@ func TestRevokeSessions(t *testing.T) { } CheckNoError(t, resp) + th.LoginBasic() + + sessions, _ = app.GetSessions(th.SystemAdminUser.Id) + session = sessions[0] + + _, resp = Client.RevokeSession(user.Id, session.Id) + CheckBadRequestStatus(t, resp) + Client.Logout() _, resp = Client.RevokeSession(user.Id, model.NewId()) CheckUnauthorizedStatus(t, resp)
app/session.go+9 −0 modified@@ -164,6 +164,15 @@ func RevokeSessionsForDeviceId(userId string, deviceId string, currentSessionId return nil } +func GetSessionById(sessionId string) (*model.Session, *model.AppError) { + if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil { + result.Err.StatusCode = http.StatusBadRequest + return nil, result.Err + } else { + return result.Data.(*model.Session), nil + } +} + func RevokeSessionById(sessionId string) *model.AppError { if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil { result.Err.StatusCode = http.StatusBadRequest
affd35071ea1Updates to session revoking in v4 (#7565)
3 files changed · +30 −1
api4/user.go+13 −1 modified@@ -926,7 +926,19 @@ func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { return } - if err := c.App.RevokeSessionById(sessionId); err != nil { + var session *model.Session + var err *model.AppError + if session, err = c.App.GetSessionById(sessionId); err != nil { + c.Err = err + return + } + + if session.UserId != c.Params.UserId { + c.SetInvalidUrlParam("user_id") + return + } + + if err := c.App.RevokeSession(session); err != nil { c.Err = err return }
api4/user_test.go+8 −0 modified@@ -1890,6 +1890,14 @@ func TestRevokeSessions(t *testing.T) { } CheckNoError(t, resp) + th.LoginBasic() + + sessions, _ = th.App.GetSessions(th.SystemAdminUser.Id) + session = sessions[0] + + _, resp = Client.RevokeSession(user.Id, session.Id) + CheckBadRequestStatus(t, resp) + Client.Logout() _, resp = Client.RevokeSession(user.Id, model.NewId()) CheckUnauthorizedStatus(t, resp)
app/session.go+9 −0 modified@@ -173,6 +173,15 @@ func (a *App) RevokeSessionsForDeviceId(userId string, deviceId string, currentS return nil } +func (a *App) GetSessionById(sessionId string) (*model.Session, *model.AppError) { + if result := <-a.Srv.Store.Session().Get(sessionId); result.Err != nil { + result.Err.StatusCode = http.StatusBadRequest + return nil, result.Err + } else { + return result.Data.(*model.Session), nil + } +} + func (a *App) RevokeSessionById(sessionId string) *model.AppError { if result := <-a.Srv.Store.Session().Get(sessionId); result.Err != nil { result.Err.StatusCode = http.StatusBadRequest
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-h564-6gc2-fcc6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2017-18878ghsaADVISORY
- github.com/mattermost/mattermost/commit/6be8113eb60cf5ddd2dc1c3f4db05cae0c183086ghsaWEB
- github.com/mattermost/mattermost/commit/8fbbd688ea2466dd0d70e9c07e9703d78f8a19a5ghsaWEB
- github.com/mattermost/mattermost/commit/affd35071ea155069979fd359726296de8aa6aafghsaWEB
- mattermost.com/security-updatesghsaWEB
- mattermost.com/security-updates/mitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.