Summary
Three Livewire admin components in shopper/framework (latest master at commit fcd0c59, released as v2.8.0) gate state-mutating actions on the read-only view_users permission. This is the same class as the issue Shopper fixed in v2.8.0 / PR #511 / GHSA-f946-9qp6-vgch — the PR moved most write actions from view_users to access_setting, but three were missed (one of them is a brand-new file added by the security commit itself).
A staff user holding only view_users + access_dashboard (a realistic "support" or "viewer" role per Shopper's own PermissionsTableSeeder) can: (1) self-escalate by granting any permission to their own role; (2) create a brand-new admin team member with a chosen password and the admin role and then log in as that user; (3) delete arbitrary permissions rows (RBAC DoS) or — when can_be_removed=true — delete entire roles.
CVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H = 8.8 (High). CWE-285 (Improper Authorization) + CWE-862 (Missing Authorization).
Vulnerable components (paths relative to repo root)
1) packages/admin/src/Livewire/Components/Settings/Team/Permissions.php
togglePermission(int $id) at line 28 calls $this->authorize('view_users');
removePermission(int $id) at line 55 calls $this->authorize('view_users');
The Permissions blade at packages/admin/resources/views/livewire/components/settings/team/permissions.blade.php line 34 emits every permission's id directly in wire:click handlers, so the attacker does not even need to guess IDs — the page itself enumerates them.
Net effect: any user who can mount the Permissions component (gated on view_users) can grant any permission row to the bound $role. Granting access_setting to the attacker's own role unlocks every action that PR #511 supposedly hardened with ->authorize('access_setting'). Granting delete_customers, edit_orders, edit_products, add_brands, etc. is direct data-modification escalation.
2) packages/admin/src/Livewire/SlideOvers/CreateTeamMember.php
mount() at line 53 calls $this->authorize('view_users');
store() at line 122 calls $this->authorize('view_users');
This file is new file mode 100755 in commit fcd0c59 — it was created as part of the security fix and inherited the same misclassified gate.
store() creates a User with email_verified_at = now(), the attacker's chosen password, and any selected role_id. The Radio::make('role_id') options filter only excludes config('shopper.admin.roles.user'), so the admin role is selectable. Log out, log in as the new account → full admin.
3) packages/admin/src/Livewire/Pages/Settings/Team/RolePermission.php
deleteAction at lines 81-90: only gated by ->visible($this->role->can_be_removed), with no ->authorize() chain.
Page-level mount (line 52) requires only view_users. For any role with can_be_removed = true, a view_users-only user can call the action and delete the role (cascading the loss of permissions for every assigned user).
Self-confirmation in the project's own test suite
The following tests are green on master @ fcd0c59 — they ARE the PoC:
tests/Admin/Livewire/Components/Settings/Team/PermissionsTest.php
line 14-16: `givePermissionTo('view_users')` only
line 36-45: "can toggle permission to role" — passes
line 74-85: "can remove permission" — passes
tests/Admin/Livewire/SlideOvers/CreateTeamMemberTest.php
line 16-18: `givePermissionTo('view_users')` only
line 29-56: "can create new team member" — passes, asserts the new user `hasRole('manager')`
A view_users-only Livewire user actor successfully toggles permissions, removes permissions, and creates a new privileged user — verified by Shopper's own regression tests.
Suggested fix
Change $this->authorize('view_users') to $this->authorize('access_setting') in:
Permissions::togglePermission
Permissions::removePermission
Permissions::mount (defence in depth, matches Team\Index)
CreateTeamMember::mount
CreateTeamMember::store
Add ->authorize('access_setting') to RolePermission::deleteAction (matches the pattern already applied to generatePermissionsAction, createPermissionAction, and Team\Index::DeleteAction).
Update the two regression tests to use access_setting instead of view_users so they accurately reflect the privilege boundary.
Resources
Credits
Reported by Vishal Shukla(@shukla304) using sechub.dev AI Agent
Support
If this disclosure was useful and if users would like to support continued open-source security research and responsible-disclosure work, they can sponsor at https://github.com/sponsors/therawdev — Shoppers thanks those who keeping open source safe.
References
Summary
Three Livewire admin components in
shopper/framework(latest master at commitfcd0c59, released as v2.8.0) gate state-mutating actions on the read-onlyview_userspermission. This is the same class as the issue Shopper fixed in v2.8.0 / PR #511 / GHSA-f946-9qp6-vgch — the PR moved most write actions fromview_userstoaccess_setting, but three were missed (one of them is a brand-new file added by the security commit itself).A staff user holding only
view_users+access_dashboard(a realistic "support" or "viewer" role per Shopper's ownPermissionsTableSeeder) can: (1) self-escalate by granting any permission to their own role; (2) create a brand-new admin team member with a chosen password and theadminrole and then log in as that user; (3) delete arbitrarypermissionsrows (RBAC DoS) or — whencan_be_removed=true— delete entire roles.CVSS 3.1:
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H= 8.8 (High). CWE-285 (Improper Authorization) + CWE-862 (Missing Authorization).Vulnerable components (paths relative to repo root)
1)
packages/admin/src/Livewire/Components/Settings/Team/Permissions.phptogglePermission(int $id)at line 28 calls$this->authorize('view_users');removePermission(int $id)at line 55 calls$this->authorize('view_users');The Permissions blade at
packages/admin/resources/views/livewire/components/settings/team/permissions.blade.phpline 34 emits every permission'siddirectly inwire:clickhandlers, so the attacker does not even need to guess IDs — the page itself enumerates them.Net effect: any user who can mount the Permissions component (gated on
view_users) can grant any permission row to the bound$role. Grantingaccess_settingto the attacker's own role unlocks every action that PR #511 supposedly hardened with->authorize('access_setting'). Grantingdelete_customers,edit_orders,edit_products,add_brands, etc. is direct data-modification escalation.2)
packages/admin/src/Livewire/SlideOvers/CreateTeamMember.phpmount()at line 53 calls$this->authorize('view_users');store()at line 122 calls$this->authorize('view_users');This file is
new file mode 100755in commitfcd0c59— it was created as part of the security fix and inherited the same misclassified gate.store()creates aUserwithemail_verified_at = now(), the attacker's chosen password, and any selectedrole_id. TheRadio::make('role_id')options filter only excludesconfig('shopper.admin.roles.user'), so theadminrole is selectable. Log out, log in as the new account → full admin.3)
packages/admin/src/Livewire/Pages/Settings/Team/RolePermission.phpdeleteActionat lines 81-90: only gated by->visible($this->role->can_be_removed), with no->authorize()chain.Page-level
mount(line 52) requires onlyview_users. For any role withcan_be_removed = true, aview_users-only user can call the action and delete the role (cascading the loss of permissions for every assigned user).Self-confirmation in the project's own test suite
The following tests are green on master @
fcd0c59— they ARE the PoC:A
view_users-only Livewire user actor successfully toggles permissions, removes permissions, and creates a new privileged user — verified by Shopper's own regression tests.Suggested fix
Change
$this->authorize('view_users')to$this->authorize('access_setting')in:Permissions::togglePermissionPermissions::removePermissionPermissions::mount(defence in depth, matchesTeam\Index)CreateTeamMember::mountCreateTeamMember::storeAdd
->authorize('access_setting')toRolePermission::deleteAction(matches the pattern already applied togeneratePermissionsAction,createPermissionAction, andTeam\Index::DeleteAction).Update the two regression tests to use
access_settinginstead ofview_usersso they accurately reflect the privilege boundary.Resources
Credits
Reported by Vishal Shukla(@shukla304) using sechub.dev AI Agent
Support
If this disclosure was useful and if users would like to support continued open-source security research and responsible-disclosure work, they can sponsor at https://github.com/sponsors/therawdev — Shoppers thanks those who keeping open source safe.
References