Shopper: Authorization bypass in Filament bulk actions allows browse-only staff to mass-delete attributes/tags and mass-toggle visibility of brands/categories/suppliers
위협 신호 · CVSS · EPSS · KEV
이론적 심각도 점수
예측 데이터 없음
실측 악용 기록 없음
계획된 패치 주기 내 조치(60일 이내)
CVSS 벡터 · 메트릭
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H상세 설명
Summary
Five Filament groupedBulkActions blocks across the Shopper admin Livewire pages omit the ->authorize(...) permission gate, while their per-record sibling actions (and other Shopper Index pages such as Pages/Settings/Currencies.php, Pages/Reviews/Index.php, Pages/Collection/Index.php, and Pages/Discount/Index.php) correctly chain ->authorize(...). Each affected page's mount() only requires the read-only browse_* permission, so a low-privilege staff user holding only the read permission can drive the bulk endpoint via the standard Livewire callTableBulkAction flow and execute state-mutating operations they were never granted. The vulnerability is the same class as GHSA-f946-9qp6-vgch and GHSA-j328-xmgp-j4q3 (read-only permission gating a write action), just on a different surface (Filament 4 groupedBulkActions rather than top-level Livewire methods).
A staff user holding only browse_attributes can permanently delete every product attribute in the catalog (cascading break of every dependent product variant). A user holding only browse_tags can permanently delete every product tag. Users holding browse_brands, browse_categories, or browse_suppliers can flip the visibility (is_enabled) of every brand/category/supplier in bulk, sabotaging storefront catalog visibility.
CVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H = 8.1 High. CWE-285 (Improper Authorization) and CWE-862 (Missing Authorization). The attacker has low privilege (browse-only staff role), no user interaction, network reachable.
Vulnerable components (paths relative to repo root)
All references are HEAD = commit ac9a760 on master (the very commit that closed the previous wave of authorization-drift bugs from GHSA-j328-xmgp-j4q3).
1) packages/admin/src/Livewire/Pages/Attribute/Browse.php
Mount at line 36–39 requires only browse_attributes.
- Lines 106–122:
DeleteBulkAction::make()has NO->authorize(...)chain (the surrounding per-recorddeleteaction at lines 95–104 correctly does->authorize('delete_attributes')). - Lines 123–138:
BulkAction::make('enabled')has NO->authorize(...). - Lines 139–155:
BulkAction::make('disabled')has NO->authorize(...).
Net effect: a browse_attributes-only user can delete every row in the attributes table, and toggle is_enabled on every attribute in one request. Deleting an attribute cascades into every product variant that references it via the attribute_product pivot.
2) packages/admin/src/Livewire/Pages/Tag/Index.php
Mount at line 39 requires only browse_tags.
- Lines 96–108:
DeleteBulkAction::make()has NO->authorize(...)chain (the per-recorddeleteaction at lines 79–94 correctly does->authorize('delete_tags')).
Net effect: a browse_tags-only user can delete every ProductTag row.
3) packages/admin/src/Livewire/Pages/Brand/Index.php
Mount at line 37–40 requires only browse_brands.
- Lines 97–112:
BulkAction::make('enabled')has NO->authorize(...). - Lines 113–129:
BulkAction::make('disabled')has NO->authorize(...).
Net effect: a browse_brands-only user can flip is_enabled on every brand. Disabling all brands removes them from the storefront catalog. The per-record edit/delete actions and the DeleteBulkAction at lines 130–148 are correctly ->authorize(...) gated — only the visibility bulk actions were missed.
4) packages/admin/src/Livewire/Pages/Category/Index.php
Mount at line 38–41 requires only browse_categories.
- Lines 102–117:
BulkAction::make('enabled')has NO->authorize(...). - Lines 118–133:
BulkAction::make('disabled')has NO->authorize(...).
Net effect: a browse_categories-only user can flip is_enabled on every category. Same shape as Brand.
5) packages/admin/src/Livewire/Pages/Supplier/Index.php
Mount at line 38 requires only browse_suppliers.
- Lines 93–108:
BulkAction::make('enabled')has NO->authorize(...). - Lines 109–125:
BulkAction::make('disabled')has NO->authorize(...).
Net effect: a browse_suppliers-only user can flip is_enabled on every supplier.
Reference comparison: places that ARE correctly gated
For reference, here is what the same pattern looks like in files that DID get the fix:
packages/admin/src/Livewire/Pages/Settings/Currencies.phplines 90–129: everyBulkActionchains->authorize('access_setting').packages/admin/src/Livewire/Pages/Reviews/Index.phplines 105–119:DeleteBulkActionchains->authorize('delete_reviews').packages/admin/src/Livewire/Pages/Collection/Index.phplines 109–128:DeleteBulkActionchains->authorize('delete_collections').packages/admin/src/Livewire/Pages/Discount/Index.phplines 126–145:DeleteBulkActionchains->authorize('delete_discounts').
The convention is established and applied elsewhere — these five files just missed it.
Proof of Concept
The attached file tests/Admin/Livewire/Pages/Brand/AuthBypassPocTest.php (added in this report) contains seven Pest tests, each acting as a browse_*-only staff user and invoking the bulk endpoint. All seven pass on master @ ac9a760:
1 PASS Tests\Admin\Livewire\Pages\Brand\AuthBypassPocTest 2 ✓ it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all brands via unguarded BulkAction 3 ✓ it SHOPPER-2 PoC: read-only viewer can mass-ENABLE all brands via unguarded BulkAction 4 ✓ it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all categories via unguarded BulkAction 5 ✓ it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all suppliers via unguarded BulkAction 6 ✓ it SHOPPER-2 PoC: read-only viewer can DELETE all attributes via unguarded DeleteBulkAction 7 ✓ it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all attributes via unguarded BulkAction 8 ✓ it SHOPPER-2 PoC: browse_tags viewer can DELETE all product tags via unguarded DeleteBulkAction 9 10 Tests: 7 passed (32 assertions)Each test seeds three records, signs in a user holding only the corresponding browse_* permission, calls Livewire::test(<Page>::class)->callTableBulkAction(...), and asserts the side effect (records flipped or deleted). For example, the attribute mass-delete test:
1$this->viewer = User::factory()->create(); 2$this->viewer->givePermissionTo('browse_attributes'); 3$this->actingAs($this->viewer); 4 5Attribute::factory()->count(3)->create(); 6expect($this->viewer->can('delete_attributes'))->toBeFalse(); 7 8Livewire::test(AttributeBrowse::class) 9 ->callTableBulkAction(\Filament\Actions\DeleteBulkAction::class, Attribute::pluck('id')->toArray())10 ->assertHasNoErrors();11 12expect(Attribute::count())->toBe(0);The call uses the same callTableBulkAction helper Shopper's own test suite uses everywhere, which in turn drives the same Livewire update payload the browser would emit — so this is a faithful HTTP-level reproduction.
Suggested fix
Add ->authorize(<correct_permission>) to each of the five vulnerable groups, mirroring the pattern already used elsewhere:
1 // Pages/Attribute/Browse.php 2 ->groupedBulkActions([ 3 DeleteBulkAction::make() 4+ ->authorize('delete_attributes') 5 ->label(__('shopper::forms.actions.delete')) 6 ->requiresConfirmation() 7 ->action(function (Collection $records): void { /* ... */ }), 8 BulkAction::make('enabled') 9+ ->authorize('edit_attributes')10 ->label(__('shopper::forms.actions.enable'))11 ->action(function (Collection $records): void { /* ... */ }),12 BulkAction::make('disabled')13+ ->authorize('edit_attributes')14 ->label(__('shopper::forms.actions.disable'))15 ->action(function (Collection $records): void { /* ... */ }),16 ])Apply the equivalent change to Pages/Tag/Index.php (delete_tags), Pages/Brand/Index.php (edit_brands for enable/disable), Pages/Category/Index.php (edit_categories), and Pages/Supplier/Index.php (edit_suppliers).
A regression test for each file (acting as a browse_*-only user and expecting assertHasErrors/AuthorizationException) would lock in the fix, matching the regression tests added for #514.
Resources
- Prior advisories of the same class (read-only permission gating a write action): GHSA-f946-9qp6-vgch, GHSA-j328-xmgp-j4q3 / GHSA-vw82-3966-f9mr.
- Same-shape fix: commit
ac9a760(PR #514). Five Filament bulk-action groups did not receive the corresponding->authorize(...)chain. - CWE-285 Improper Authorization, CWE-862 Missing Authorization.
Credits
Reported by Vishal Shukla(@shukla304) using sechub.dev AI Agent
Support
If this disclosure was useful and userswould like to support continued open-source security research and responsible-disclosure work, they can sponsor at https://github.com/sponsors/therawdev — Shopper is thankful for those keeping open source safe.
AI 심층 분석
공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.