GitHub Advisoryadvisory · Packagist

GHSA-2xc6-348p-c2x6 Sylius affected by IDOR in Cart and Checkout LiveComponents

HIGH Published 2026-03-11 Modified 2026-03-11 Official page ↗
Aliases
CVE-2026-31820
Ecosystem
Packagist
Packages
sylius/sylius
Severity
High
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
Weaknesses
CWE-639
Reviewed
2026-03-11

Affected packages

• Packagist sylius/sylius: >= 2.0.0, < 2.0.16 → fixed in 2.0.16 • Packagist sylius/sylius: >= 2.1.0, < 2.1.12 → fixed in 2.1.12 • Packagist sylius/sylius: >= 2.2.0, < 2.2.3 → fixed in 2.2.3

Description

Impact An authenticated Insecure Direct Object Reference (IDOR) vulnerability exists in multiple shop LiveComponents due to unvalidated resource IDs accepted via `#[LiveArg]` parameters. Unlike props, which are protected by LiveComponent's `@checksum`, `args` are fully user-controlled - any action that accepts a resource ID via `#[LiveArg]` and loads it with `->find()` without ownership validation is vulnerable. Checkout address FormComponent (`addressFieldUpdated` action): Accepts an `addressId` via `#[LiveArg]` and loads it without verifying ownership, exposing another user's first name, last name, company, phone number, street, city, postcode, and country. Cart WidgetComponent (`refreshCart` action): Accepts a `cartId` via `#[LiveArg]` and loads any order directly from the repository, exposing order total and item count. Cart SummaryComponent (`refreshCart` action): Accepts a `cartId` via `#[LiveArg]` and loads any order directly from the repository, exposing subtotal, discount, shipping cost, taxes (excluded and included), and order total. Since `sylius_order` contains both active carts (`state=cart`) and completed orders (`state=new/fulfilled`) in the same ID space, the cart IDOR exposes data from all orders, not just active carts. Patches The issue is fixed in versions: 2.0.16, 2.1.12, 2.2.3 and above. Workarounds Override vulnerable LiveComponent classes at the project level to add authorization checks to `#[LiveArg]` parameters. Step 1. Exclude component overrides from default autowiring In `config/services.yaml`, add `Twig/Component` to the exclude list to prevent duplicate service registration: ```yaml App\: resource: '../src/*' exclude: '../src/{Entity,Kernel.php,Twig/Components}' ``` Step 2. Override checkout address FormComponent Create `src/Twig/Components/Checkout/Address/FormComponent.php`: ```php */ use ResourceFormComponentTrait; use TemplatePropTrait; #[LiveProp] public bool $emailExists = false; /** • @param OrderRepositoryInterface $repository • @param UserRepositoryInterface $shopUserRepository */ public function __construct( OrderRepositoryInterface $repository, FormFactoryInterface $formFactory, string $resourceClass, string $formClass, protected readonly CustomerContextInterface $customerContext, protected readonly UserRepositoryInterface $shopUserRepository, protected readonly AddressRepositoryInterface $addressRepository, ) { $this->initialize($repository, $formFactory, $resourceClass, $formClass); } #[PreReRender(priority: -100)] public function checkEmailExist(): void { $email = $this->formValues['customer']['email'] ?? null; if (null !== $email) { $this->emailExists = $this->shopUserRepository->findOneByEmail($email) !== null; } } #[LiveListener(AddressBookComponent::SYLIUS_SHOP_ADDRESS_UPDATED)] public function addressFieldUpdated(#[LiveArg] mixed $addressId, #[LiveArg] string $field): void { $customer = $this->customerContext->getCustomer(); if (null === $customer) { return; } // Fix: findOneByCustomer instead of find — validates ownership $address = $this->addressRepository->findOneByCustomer((string) $addressId, $customer); if (null === $address) { return; } $newAddress = []; $newAddress['firstName'] = $address->getFirstName(); $newAddress['lastName'] = $address->getLastName(); $newAddress['phoneNumber'] = $address->getPhoneNumber(); $newAddress['company'] = $address->getCompany(); $newAddress['countryCode'] = $address->getCountryCode(); if ($address->getProvinceCode() !== null) { $newAddress['provinceCode'] = $address->getProvinceCode(); } if ($address->getProvinceName() !== null) { $newAddress['provinceName'] = $address->getProvinceName(); } $newAddress['street'] = $address->getStreet(); $newAddress['city'] = $address->getCity(); $newAddress['postcode'] = $address->getPostcode(); $this->formValues[$field] = $newAddress; } protected function instantiateForm(): FormInterface { return $this->formFactory->create( $this->formClass, $this->resource, ['customer' => $this->customerContext->getCustomer()], ); } } ``` Step 3. Override cart WidgetComponent Create `src/Twig/Components/Cart/WidgetComponent.php`: ```php */ use ResourceLivePropTrait; #[LiveProp(hydrateWith: 'hydrateResource', dehydrateWith: 'dehydrateResource')] public ?ResourceInterface $cart = null; public function __construct( protected readonly CartContextInterface $cartContext, OrderRepositoryInterface $orderRepository, ) { $this->initialize($orderRepository); } #[PreMount] public function initializeCart(): void { $this->cart = $this->getCart(); } #[LiveListener(FormComponent::SYLIUS_SHOP_CART_CHANGED)] #[LiveListener(FormComponent::SYLIUS_SHOP_CART_CLEARED)] public function refreshCart(#[LiveArg] mixed $cartId = null): void { // Fix: ignore user-supplied cartId, always load from session $this->cart = $this->getCart(); } private function getCart(): ?OrderInterface { try { return $this->cartContext->getCart(); } catch (CartNotFoundException) { return null; } return $cart; } } ``` Step 4. Override cart SummaryComponent Create `src/Twig/Components/Cart/SummaryComponent.php`: ```php <?php declare(strict_types=1); …

Related

Alias · CVE (1)

  • CVE-2026-31820 Sylius is an Open Source eCommerce Framework on Symfony. vulnerability

Weakness · CWE (1)

  • CWE-639 Authorization Bypass Through User-Controlled Key weakness

References