VYPR
Moderate severityNVD Advisory· Published Feb 24, 2025· Updated Feb 24, 2025

Channel export permitted on archived channel when viewing archived channels is disabled

CVE-2025-24526

Description

Mattermost versions 10.1.x <= 10.1.3, 10.4.x <= 10.4.1, 9.11.x <= 9.11.7, 10.3.x <= 10.3.2, 10.2.x <= 10.2.2 fail to restrict channel export of archived channels when the "Allow users to view archived channels" is disabled which allows a user to export channel contents when they shouldn't have access to it

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/mattermost/mattermost/server/v8Go
< 8.0.0-20250110161910-96195f1bd7468.0.0-20250110161910-96195f1bd746
github.com/mattermost/mattermost/server/v8Go
>= 9.11.0-rc1, < 9.11.89.11.8
github.com/mattermost/mattermost/server/v8Go
>= 10.2.0-rc1, < 10.2.310.2.3
github.com/mattermost/mattermost/server/v8Go
>= 10.3.0-rc1, < 10.3.310.3.3
github.com/mattermost/mattermost/server/v8Go
>= 10.4.0-rc1, < 10.4.210.4.2

Affected products

1

Patches

2
96195f1bd746

Prepackage Channel Export plugin v1.2.1 (#29638)

https://github.com/mattermost/mattermostDoug LauderJan 10, 2025via ghsa
1 file changed · +1 0
  • server/Makefile+1 0 modified
    @@ -161,6 +161,7 @@ PLUGIN_PACKAGES += mattermost-plugin-user-survey-v1.1.1
     PLUGIN_PACKAGES += mattermost-plugin-mscalendar-v1.3.4
     PLUGIN_PACKAGES += mattermost-plugin-msteams-meetings-v2.2.0
     PLUGIN_PACKAGES += mattermost-plugin-metrics-v0.5.3
    +PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.2.1
     
     EE_PACKAGES=$(shell $(GO) list $(BUILD_ENTERPRISE_DIR)/...)
     
    
3c052b66207f

[GH-51] Added conditional to disallow export in archived channel when it is not visible to users (#55)

2 files changed · +132 0
  • server/api.go+10 0 modified
    @@ -143,6 +143,16 @@ func (h *Handler) Export(w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    +	areArchivedChannelsVisible := true
    +	if h.client.Configuration.GetConfig().TeamSettings.ExperimentalViewArchivedChannels == nil || !*h.client.Configuration.GetConfig().TeamSettings.ExperimentalViewArchivedChannels {
    +		areArchivedChannelsVisible = false
    +	}
    +
    +	if channel.DeleteAt > 0 && !areArchivedChannelsVisible {
    +		handleError(w, http.StatusNotFound, "channel '%s' is archived and not visible anymore", channelID)
    +		return
    +	}
    +
     	if !h.plugin.hasPermissionToExportChannel(userID, channelID) {
     		handleError(w, http.StatusForbidden, "user does not have permission to export channels")
     		return
    
  • server/api_test.go+122 0 modified
    @@ -2,6 +2,7 @@ package main
     
     import (
     	"bytes"
    +	"fmt"
     	"io"
     	"io/ioutil"
     	"net/http"
    @@ -138,6 +139,11 @@ func TestHandler(t *testing.T) {
     		mockChannel.EXPECT().Get(channelID).Return(&model.Channel{Id: channelID}, nil).Times(1)
     		mockUser.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionReadChannel).Return(true).Times(1)
     		mockUser.EXPECT().HasPermissionTo(userID, model.PermissionManageSystem).Return(false).Times(1)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
    +			TeamSettings: model.TeamSettings{
    +				ExperimentalViewArchivedChannels: &trueValue,
    +			},
    +		}).Times(2)
     		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
     			PrivacySettings: model.PrivacySettings{
     				ShowEmailAddress: &trueValue,
    @@ -367,6 +373,11 @@ func TestHandler(t *testing.T) {
     		mockChannel.EXPECT().Get(channelID).Return(&model.Channel{Id: channelID}, nil).Times(1)
     		mockUser.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionReadChannel).Return(true).Times(1)
     		mockUser.EXPECT().HasPermissionTo(userID, model.PermissionManageSystem).Return(false).Times(1)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
    +			TeamSettings: model.TeamSettings{
    +				ExperimentalViewArchivedChannels: &trueValue,
    +			},
    +		}).Times(2)
     		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
     			PrivacySettings: model.PrivacySettings{
     				ShowEmailAddress: &falseValue,
    @@ -414,6 +425,11 @@ func TestHandler(t *testing.T) {
     		mockChannel.EXPECT().Get(channelID).Return(&model.Channel{Id: channelID}, nil).Times(1)
     		mockUser.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionReadChannel).Return(true).Times(1)
     		mockUser.EXPECT().HasPermissionTo(userID, model.PermissionManageSystem).Return(false).Times(1)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
    +			TeamSettings: model.TeamSettings{
    +				ExperimentalViewArchivedChannels: &trueValue,
    +			},
    +		}).Times(2)
     		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
     			PrivacySettings: model.PrivacySettings{
     				ShowEmailAddress: &trueValue,
    @@ -464,6 +480,11 @@ func TestHandler(t *testing.T) {
     		mockChannel.EXPECT().Get(channelID).Return(&model.Channel{Id: channelID}, nil).Times(1)
     		mockUser.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionReadChannel).Return(true).Times(1)
     		mockUser.EXPECT().HasPermissionTo(userID, model.PermissionManageSystem).Return(false).Times(1)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
    +			TeamSettings: model.TeamSettings{
    +				ExperimentalViewArchivedChannels: &trueValue,
    +			},
    +		}).Times(2)
     		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
     			PrivacySettings: model.PrivacySettings{
     				ShowEmailAddress: &trueValue,
    @@ -497,4 +518,105 @@ func TestHandler(t *testing.T) {
     			require.Equal(t, "a channel export is already running.", err.Error())
     		}
     	})
    +
    +	t.Run("export when channel is archived and not visible", func(t *testing.T) {
    +		mockCtrl := gomock.NewController(t)
    +
    +		mockChannel := mock_pluginapi.NewMockChannel(mockCtrl)
    +		mockFile := mock_pluginapi.NewMockFile(mockCtrl)
    +		mockLog := mock_pluginapi.NewMockLog(mockCtrl)
    +		mockPost := mock_pluginapi.NewMockPost(mockCtrl)
    +		mockSlashCommand := mock_pluginapi.NewMockSlashCommand(mockCtrl)
    +		mockUser := mock_pluginapi.NewMockUser(mockCtrl)
    +		mockSystem := mock_pluginapi.NewMockSystem(mockCtrl)
    +		mockConfiguration := mock_pluginapi.NewMockConfiguration(mockCtrl)
    +		mockCluster := mock_pluginapi.NewMockCluster(mockCtrl)
    +		mockCluster.EXPECT().NewMutex(gomock.Eq(KeyClusterMutex)).Return(pluginapi.NewClusterMutexMock(), nil)
    +
    +		mockAPI := pluginapi.CustomWrapper(mockChannel, mockFile, mockLog, mockPost, mockSlashCommand, mockUser, mockSystem, mockConfiguration, mockCluster)
    +
    +		now := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.FixedZone("UTC-8", -8*60*60))
    +		userID := "user_id"
    +		channelID := "channel_id"
    +		address := setupAPI(t, mockAPI, now, userID, channelID)
    +		client := NewClient(address)
    +		client.SetToken("token")
    +
    +		mockSystem.EXPECT().GetLicense().Return(&model.License{Features: &model.Features{
    +			FutureFeatures: &trueValue,
    +		}}).Times(2)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{}).Times(1)
    +		mockChannel.EXPECT().Get(channelID).Return(&model.Channel{Id: channelID, DeleteAt: 1}, nil).Times(1)
    +		mockUser.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionReadChannel).Return(true).Times(1)
    +		mockUser.EXPECT().HasPermissionTo(userID, model.PermissionManageSystem).Return(false).Times(1)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
    +			TeamSettings: model.TeamSettings{
    +				ExperimentalViewArchivedChannels: &falseValue,
    +			},
    +		}).Times(2)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
    +			PrivacySettings: model.PrivacySettings{
    +				ShowEmailAddress: &trueValue,
    +			},
    +		})
    +
    +		var buffer bytes.Buffer
    +		err := client.ExportChannel(&buffer, channelID, FormatCSV)
    +		require.EqualValues(t, "", buffer.String())
    +
    +		expectedErr := fmt.Sprintf("channel '%s' is archived and not visible anymore", channelID)
    +
    +		require.EqualError(t, err, expectedErr)
    +	})
    +
    +	t.Run("export when channel is archived and visible", func(t *testing.T) {
    +		mockCtrl := gomock.NewController(t)
    +
    +		mockChannel := mock_pluginapi.NewMockChannel(mockCtrl)
    +		mockFile := mock_pluginapi.NewMockFile(mockCtrl)
    +		mockLog := mock_pluginapi.NewMockLog(mockCtrl)
    +		mockPost := mock_pluginapi.NewMockPost(mockCtrl)
    +		mockSlashCommand := mock_pluginapi.NewMockSlashCommand(mockCtrl)
    +		mockUser := mock_pluginapi.NewMockUser(mockCtrl)
    +		mockSystem := mock_pluginapi.NewMockSystem(mockCtrl)
    +		mockConfiguration := mock_pluginapi.NewMockConfiguration(mockCtrl)
    +		mockCluster := mock_pluginapi.NewMockCluster(mockCtrl)
    +		mockCluster.EXPECT().NewMutex(gomock.Eq(KeyClusterMutex)).Return(pluginapi.NewClusterMutexMock(), nil)
    +
    +		mockAPI := pluginapi.CustomWrapper(mockChannel, mockFile, mockLog, mockPost, mockSlashCommand, mockUser, mockSystem, mockConfiguration, mockCluster)
    +
    +		now := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.FixedZone("UTC-8", -8*60*60))
    +		userID := "user_id"
    +		channelID := "channel_id"
    +		address := setupAPI(t, mockAPI, now, userID, channelID)
    +		client := NewClient(address)
    +		client.SetToken("token")
    +
    +		mockSystem.EXPECT().GetLicense().Return(&model.License{Features: &model.Features{
    +			FutureFeatures: &trueValue,
    +		}}).Times(2)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{}).Times(1)
    +		mockChannel.EXPECT().Get(channelID).Return(&model.Channel{Id: channelID, DeleteAt: 1}, nil).Times(1)
    +		mockUser.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionReadChannel).Return(true).Times(1)
    +		mockUser.EXPECT().HasPermissionTo(userID, model.PermissionManageSystem).Return(false).Times(1)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
    +			TeamSettings: model.TeamSettings{
    +				ExperimentalViewArchivedChannels: &trueValue,
    +			},
    +		}).Times(2)
    +		mockConfiguration.EXPECT().GetConfig().Return(&model.Config{
    +			PrivacySettings: model.PrivacySettings{
    +				ShowEmailAddress: &trueValue,
    +			},
    +		})
    +
    +		var buffer bytes.Buffer
    +		err := client.ExportChannel(&buffer, channelID, FormatCSV)
    +		require.Nil(t, err)
    +
    +		expected := `Post Creation Time,User Id,User Email,User Type,User Name,Post Id,Parent Post Id,Post Message,Post Type
    +2009-11-11 07:00:00 +0000 UTC,post_user_id,post_user_email,user,post_user_nickname,post_id,post_parent_id,post_message,message
    +`
    +		require.EqualValues(t, expected, buffer.String())
    +	})
     }
    

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

6

News mentions

0

No linked articles in our index yet.