The One Endpoint That Forgot to Sanitize: Guest Visibility Bypass & Admin Oracle in Mattermost Boards
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-boards — server/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) andhandleGetUser(users.go:274). - No
app.SanitizeProfile(...). Siblings:users.go:98andusers.go:287. - It additionally stamps
ManageTeam/ManageSystemflags 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
- 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. POST /api/v2/teams/{T}/userswith body["<userID1>","<userID2>"]and headerX-Requested-With: XMLHttpRequest.- The response returns each user's
Username/Nickname/Rolesunsanitized, plus aPermissionsarray revealingManageTeam/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.