{"id":"CVE-2026-41128","aliases":["GHSA-jq2f-59pj-p3m3"],"url":"https://o3.security/vulnerability/CVE-2026-41128","summary":"Craft CMS has a Missing Authorization Check on User Group Removal via save-permissions Action","details":"## Summary\n\nThe `actionSavePermissions()` endpoint allows a user with only `viewUsers` permission to remove arbitrary users from all user groups. While `_saveUserGroups()` enforces per-group authorization for additions, it performs no equivalent authorization check for removals, so submitting an empty `groups` value removes all existing group memberships.\n\n## Affected Versions\n\n- Craft CMS 5.6.0 through 5.9.14 (latest release at time of report)\n- Regression introduced in 5.6.0 when the `viewUsers` permission was added\n- Prior to 5.6.0, `editedUser()` required `editUsers`, which implicitly protected this endpoint\n- Requires Pro edition or higher (the vulnerable code path is gated by `CmsEdition::Pro`)\n\n## Vulnerability Details\n\n### Root Cause\n\nThis is a **regression** introduced in Craft CMS 5.6.0 when the `viewUsers` permission was added. Before that change, `editedUser()` required `editUsers` permission for accessing other users’ data, which implicitly protected `actionSavePermissions()`. After the change, `actionSavePermissions()` became reachable for users with read-only access to other users, but the underlying group-saving logic still lacked authorization for group removals.\n\nThe vulnerability has two components:\n\n1. **`actionSavePermissions()` reachable with read-only access**: The action only requires a control panel request and delegates to `editedUser()`, which now only checks `viewUsers` — a permission explicitly documented as \"read-only access to user elements.\"\n\n2. **Asymmetric authorization in `_saveUserGroups()`**: The method checks `assignUserGroup` permission only when **adding** a user to a new group. When the `groups` parameter is an empty string (resulting in an empty array), the loop is skipped entirely, no authorization checks are run, and all group memberships are removed.\n\n### Prerequisites\n\n- Attacker has a control panel account with `accessCp` and `viewUsers` permissions only\n- Target user belongs to one or more user groups that grant additional permissions\n- Pro edition or higher\n\n### Attack Steps\n\n1. Attacker authenticates to the Control Panel\n2. Attacker sends a POST request to `actions/users/save-permissions` with:\n   - `userId` = target user's ID\n   - `groups` = `` (empty string)\n3. All group memberships for the target user are removed\n4. All permissions inherited from those groups are immediately revoked\n\n### Impact\n\n- **Privilege revocation**: An attacker can strip group-based permissions from arbitrary users, including accounts whose effective access derives from group membership\n- **Denial of access**: Users lose access to sections, volumes, and features that were granted through group membership\n- **Bypass of elevated session requirement**: Group removal does not trigger `requireElevatedSession()` (which is only triggered when new groups are added)","published":"2026-04-21T23:32:37.911Z","modified":"2026-08-12T03:51:18.445672854Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"craftcms/cms","fixedVersion":"5.9.15"}],"fix":{"url":"https://github.com/craftcms/cms/commit/b135384808ad43fcf8836a9dd9b877fb0087bc27","label":"craftcms/cms@b135384"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/41xxx/CVE-2026-41128.json"},{"type":"ADVISORY","url":"https://github.com/craftcms/cms/security/advisories/GHSA-jq2f-59pj-p3m3"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41128"},{"type":"FIX","url":"https://github.com/craftcms/cms/commit/b135384808ad43fcf8836a9dd9b877fb0087bc27"},{"type":"PACKAGE","url":"https://github.com/craftcms/cms"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:18.445672854Z"}}