← All writeups
Low Bugcrowd · Mattermost Duplicate

The One Endpoint That Forgot to Sanitize: Guest Visibility Bypass & Admin Oracle in Mattermost Boards

access-controlinformation-disclosuremattermostguest-boundarysibling-path-diffing

By Kavennesh Balachandar · Bugcrowd · Mattermost Bug Bounty Program

# TL;DR

The bulk user-lookup endpoint POST /api/v2/teams/{teamID}/users (handleGetTeamUsersByID) in mattermost-plugin-boards returns profiles for caller-supplied user IDs but omits the two protections both sibling endpoints apply: it never runs the CanSeeUser guest-visibility filter, never runs SanitizeProfile, and additionally stamps each returned user with ManageTeam/ManageSystem flags — a reliable is-admin oracle. A System Guest scoped to shared channels can thus resolve arbitrary user IDs to unsanitized profiles and learn which users are admins. Severity: Low (P4), CVSS ~4.3 — information disclosure, not full PII leak (email isn't exposed).

# Another sibling-diff

Same technique as the Jira template finding: a group of endpoints that should share protections, and the one that doesn't. Boards has three user-lookup handlers. Two apply the guest-visibility filter and profile sanitization; the bulk one applies neither.

# The vulnerable handler

mattermost-plugin-boardsserver/api/teams.go:238-299, handleGetTeamUsersByID. Its only gate is team-level:

// teams.go:263
HasPermissionToTeam(caller, teamID, PermissionViewTeam)

What it omits, relative to its siblings:

  • No app.CanSeeUser(...) guest shared-channel visibility filter. Siblings: handleGetUsersList (users.go:87) and handleGetUser (users.go:274).
  • No app.SanitizeProfile(...). Siblings: users.go:98 and users.go:287.
  • It additionally stamps ManageTeam/ManageSystem flags onto each returned user (teams.go:282-289) — the admin oracle.

So a System Guest who belongs to teamID bypasses the per-user guest-visibility restriction the siblings enforce.

# Steps to reproduce

  1. As a System Guest in team T (visibility should be limited to shared channels), obtain one or more 26-char user IDs — from posts/mentions in your channels, or any other source.
  2. POST /api/v2/teams/{T}/users with body ["<userID1>","<userID2>"] and header X-Requested-With: XMLHttpRequest.
  3. The response returns each user's Username/Nickname/Roles unsanitized, plus a Permissions array revealing ManageTeam/ManageSystem — i.e. whether each target is an admin.

# Impact and severity

A guest or any low-privilege team member can resolve arbitrary user IDs to profile data and enumerate which users are system/team administrators, bypassing the guest-visibility boundary the sibling endpoints enforce. That's useful recon for targeted attacks — identifying admins to phish. Email is not exposed (json:"-"), and names follow ShowFullName, so it's information disclosure rather than a full PII leak.

Severity: Low — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N (~4.3) / P4 (arguably P3 given the guest-boundary crossing plus the admin oracle).

# Remediation

In the teams.go:282 loop, apply the CanSeeUser filter and SanitizeProfile that the sibling handlers use, and do not stamp admin permission flags for users the caller cannot see.

# Disclosure timeline

  • 2026-06-26 — Reported to Mattermost via Bugcrowd.
  • Closed as duplicate / informational.
  • Public disclosure pending program approval.

# References