VYPR
Low severityNVD Advisory· Published May 30, 2025· Updated May 30, 2025

Improper Access Control in Mattermost allows System Managers to view team details despite role restrictions

CVE-2025-3611

Description

Mattermost versions 10.7.x <= 10.7.0, 10.5.x <= 10.5.3, 9.11.x <= 9.11.12 fails to properly enforce access control restrictions for System Manager roles, allowing authenticated users with System Manager privileges to view team details they should not have access to via direct API requests to team endpoints, even when explicitly configured with 'No access' to Teams in the System Console.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/mattermost/mattermost/server/v8Go
>= 10.6.0-rc1, < 10.7.110.7.1
github.com/mattermost/mattermost/server/v8Go
>= 10.0.0-rc1, < 10.5.410.5.4
github.com/mattermost/mattermost/server/v8Go
>= 9.0.0-rc1, < 9.11.139.11.13
github.com/mattermost/mattermost/server/v8Go
< 8.0.0-20250414154356-6f33b721de768.0.0-20250414154356-6f33b721de76

Affected products

1

Patches

1
6f33b721de76

MM-63378: Test and fix permission issues with System Manager team access (#30672)

https://github.com/mattermost/mattermostJesse HallamApr 14, 2025via ghsa
4 files changed · +179 1
  • e2e-tests/playwright/specs/functional/system_console/permissions/team_access.spec.ts+177 0 added
    @@ -0,0 +1,177 @@
    +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
    +// See LICENSE.txt for license information.
    +
    +import {UserProfile} from '@mattermost/types/users';
    +
    +import {expect, PlaywrightExtended, test} from '@mattermost/playwright-lib';
    +
    +// setupSystemManagerRole configures the system manager with the given permission ("Can Edit", "Read only", "No access")
    +// for the given section and subsection (e.g. "permission_section_reporting_site_statistics" and "permission_section_reporting_team_statistics").
    +//
    +// We do this via the system console and not the API because this page has multiple queries to build up the
    +// final API call that are all ultimately part of the spec, and we want to test the effects of those, not
    +// merely a hand-created version of the same.
    +const setupDefaultSystemManagerRole = async (
    +    pw: PlaywrightExtended,
    +    adminUser: UserProfile,
    +    sectionTestId: string,
    +    subsectionTestId: string,
    +    permissionText: string,
    +) => {
    +    // Login as admin and navigate to System Console
    +    const {systemConsolePage: adminConsolePage} = await pw.testBrowser.login(adminUser);
    +
    +    // Go to System Console
    +    await adminConsolePage.goto();
    +
    +    // Login to the System Console and navigate to Delegated Granular Administration
    +    await adminConsolePage.sidebar.goToItem('Delegated Granular Administration');
    +
    +    // Find the System Manager row in the table
    +    const systemManagerText = adminConsolePage.page.getByText('System Manager', {exact: true}).first();
    +    await expect(systemManagerText).toBeVisible();
    +
    +    // Click on the System Manager text to go to its settings page
    +    await systemManagerText.click();
    +
    +    // Expand the section
    +    const sectionReporting = adminConsolePage.page.locator(`data-testid=${sectionTestId}`);
    +    const hideSubsectionsLink = sectionReporting.getByRole('button').filter({hasText: 'Hide'}).first();
    +    const showSubsectionsLink = sectionReporting.getByRole('button').filter({hasText: 'Show'}).first();
    +
    +    // Check which one is visible and click if needed
    +    const isHideVisible = await hideSubsectionsLink.isVisible();
    +    const isShowVisible = !isHideVisible && (await showSubsectionsLink.isVisible());
    +
    +    if (isShowVisible) {
    +        // Need to expand
    +        await showSubsectionsLink.click();
    +    }
    +
    +    // Get the whole row
    +    const rowReporting = adminConsolePage.page.locator('.PermissionRow').filter({has: sectionReporting});
    +    await rowReporting.click();
    +
    +    // Find the sub section
    +    const subsectionTeamStatistics = rowReporting.locator(`data-testid=${subsectionTestId}`);
    +
    +    await subsectionTeamStatistics.click();
    +
    +    // Look for dropdown button
    +    const dropdownButton = subsectionTeamStatistics.locator('button').first();
    +
    +    // Click the button to open the dropdown menu
    +    await dropdownButton.click();
    +
    +    // Click on the desired option in the dropdown
    +    const permissionOption = subsectionTeamStatistics.locator('.dropdown-menu').getByText(permissionText).first();
    +    await permissionOption.click();
    +
    +    // Click Save button
    +    const saveButton = adminConsolePage.page.getByRole('button', {name: 'Save'}).first();
    +    await saveButton.click();
    +
    +    // Wait for save operation to complete
    +    await adminConsolePage.page.waitForLoadState('networkidle');
    +
    +    // Go back to the main console
    +    await adminConsolePage.goto();
    +    await adminConsolePage.page.waitForLoadState('networkidle');
    +};
    +
    +test('MM-63378 System Manager without team access permissions cannot view team details', async ({pw}) => {
    +    const {
    +        adminUser,
    +        adminClient,
    +        user: systemManagerUser,
    +        userClient: systemManagerClient,
    +        team,
    +    } = await pw.initSetup();
    +
    +    // Update user with system_manager role
    +    await adminClient.updateUserRoles(systemManagerUser.id, 'system_user system_manager');
    +
    +    // Create another team of which the user is not a member.
    +    const otherTeam = await adminClient.createTeam(pw.random.team());
    +
    +    // Login as the user
    +    const {systemConsolePage} = await pw.testBrowser.login(systemManagerUser);
    +
    +    // Configure the system manager with the default permissions.
    +    await setupDefaultSystemManagerRole(
    +        pw,
    +        adminUser,
    +        'permission_section_reporting',
    +        'permission_section_reporting_team_statistics',
    +        'Can edit',
    +    );
    +    await setupDefaultSystemManagerRole(
    +        pw,
    +        adminUser,
    +        'permission_section_user_management',
    +        'permission_section_user_management_teams',
    +        'Can edit',
    +    );
    +
    +    // Verify the system manager has access to the site statistics for all teams
    +    await systemConsolePage.goto();
    +
    +    // Navigate to Team Statistics
    +    await systemConsolePage.sidebar.goToItem('Team Statistics');
    +
    +    // Wait for page to fully load
    +    await systemConsolePage.page.waitForLoadState('networkidle');
    +
    +    // Find the team filter dropdown
    +    let teamFilterSelect = systemConsolePage.page.getByTestId('teamFilter');
    +    await expect(teamFilterSelect).toBeVisible();
    +
    +    // Select the team by value
    +    await teamFilterSelect.selectOption({value: team.id});
    +
    +    // Verify the text shows "Team Statistics for <team name>"
    +    let teamStatsHeading = systemConsolePage.page.getByText(`Team Statistics for ${team.display_name}`, {exact: true});
    +    await expect(teamStatsHeading).toBeVisible();
    +
    +    // Select the other team by value
    +    await teamFilterSelect.selectOption({value: otherTeam.id});
    +
    +    // Verify the text shows "Team Statistics for <team name>"
    +    const otherTeamStatsHeading = systemConsolePage.page.getByText(`Team Statistics for ${otherTeam.display_name}`, {
    +        exact: true,
    +    });
    +    await expect(otherTeamStatsHeading).toBeVisible();
    +
    +    // Verify the user has API access to the otherTeam.
    +    const fetchedOtherTeam = await systemManagerClient.getTeam(otherTeam.id);
    +    expect(fetchedOtherTeam.id).toEqual(otherTeam.id);
    +
    +    // Configure the system manager without access to team user management
    +    await setupDefaultSystemManagerRole(
    +        pw,
    +        adminUser,
    +        'permission_section_user_management',
    +        'permission_section_user_management_teams',
    +        'No access',
    +    );
    +
    +    // Verify the system manager only has access to the site statistics for the team they belong to
    +    await systemConsolePage.goto();
    +
    +    // Navigate to Team Statistics
    +    await systemConsolePage.sidebar.goToItem('Team Statistics');
    +
    +    // Find the team filter dropdown
    +    teamFilterSelect = systemConsolePage.page.getByTestId('teamFilter');
    +    await expect(teamFilterSelect).toBeVisible();
    +
    +    // Select the team by value
    +    await teamFilterSelect.selectOption({value: team.id});
    +
    +    // Verify the text shows "Team Statistics for <team name>"
    +    teamStatsHeading = systemConsolePage.page.getByText(`Team Statistics for ${team.display_name}`, {exact: true});
    +    await expect(teamStatsHeading).toBeVisible();
    +
    +    // Verify the user has no API access to the otherTeam.
    +    await expect(systemManagerClient.getTeam(otherTeam.id)).rejects.toThrow();
    +});
    
  • server/public/model/role.go+0 1 modified
    @@ -109,7 +109,6 @@ func init() {
     			PermissionGetAnalytics,
     		},
     		PermissionSysconsoleReadReportingTeamStatistics.Id: {
    -			PermissionViewTeam,
     			PermissionGetAnalytics,
     		},
     		PermissionSysconsoleWriteUserManagementUsers.Id: {
    
  • webapp/channels/src/components/admin_console/system_roles/system_role/__snapshots__/system_role_permission.test.tsx.snap+1 0 modified
    @@ -6,6 +6,7 @@ exports[`admin_console/system_role_permission should match snapshot 1`] = `
     >
       <div
         className="PermissionSection"
    +    data-testid="permission_section_environment"
         key="environment"
       >
         <div
    
  • webapp/channels/src/components/admin_console/system_roles/system_role/system_role_permission.tsx+1 0 modified
    @@ -84,6 +84,7 @@ export default class SystemRolePermission extends React.PureComponent<Props> {
             return (
                 <div
                     key={section.name}
    +                data-testid={`permission_section_${section.name}`}
                     className='PermissionSection'
                 >
                     <div className='PermissionSectionText'>
    

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

4

News mentions

0

No linked articles in our index yet.