Alta

Sylius affected by IDOR in Cart and Checkout LiveComponents

  • Publicada: 11.03.2026
  • Actualizado: 11.10.2026 10:22

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
<?php

declare(strict_types=1);

namespace App\Twig\Components\Checkout\Address;

use Sylius\Bundle\ShopBundle\Twig\Component\Checkout\Address\AddressBookComponent;
use Sylius\Bundle\UiBundle\Twig\Component\ResourceFormComponentTrait;
use Sylius\Bundle\UiBundle\Twig\Component\TemplatePropTrait;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Component\Core\Repository\AddressRepositoryInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Customer\Context\CustomerContextInterface;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveListener;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\Attribute\PreReRender;

#[AsLiveComponent]
class FormComponent
{
    /** @use ResourceFormComponentTrait<OrderInterface> */
    use ResourceFormComponentTrait;
    use TemplatePropTrait;

    #[LiveProp]
    public bool $emailExists = false;

    /**
     * @param OrderRepositoryInterface<OrderInterface> $repository
     * @param UserRepositoryInterface<ShopUserInterface> $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
<?php

declare(strict_types=1);

namespace App\Twig\Components\Cart;

use Sylius\Bundle\ShopBundle\Twig\Component\Cart\FormComponent;
use Sylius\Bundle\UiBundle\Twig\Component\ResourceLivePropTrait;
use Sylius\Bundle\UiBundle\Twig\Component\TemplatePropTrait;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Order\Context\CartContextInterface;
use Sylius\Component\Order\Context\CartNotFoundException;
use Sylius\Resource\Model\ResourceInterface;
use Sylius\TwigHooks\LiveComponent\HookableLiveComponentTrait;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveListener;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\TwigComponent\Attribute\PreMount;

#[AsLiveComponent]
class WidgetComponent
{
    use DefaultActionTrait;
    use HookableLiveComponentTrait;
    use TemplatePropTrait;

    /** @use ResourceLivePropTrait<OrderInterface> */
    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);

namespace App\Twig\Components\Cart;

use Sylius\Bundle\ShopBundle\Twig\Component\Cart\FormComponent;
use Sylius\Bundle\UiBundle\Twig\Component\ResourceLivePropTrait;
use Sylius\Bundle\UiBundle\Twig\Component\TemplatePropTrait;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Resource\Model\ResourceInterface;
use Sylius\TwigHooks\LiveComponent\HookableLiveComponentTrait;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveListener;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class SummaryComponent
{
    use DefaultActionTrait;
    use HookableLiveComponentTrait;

    /** @use ResourceLivePropTrait<OrderInterface> */
    use ResourceLivePropTrait;
    use TemplatePropTrait;

    #[LiveProp(hydrateWith: 'hydrateResource', dehydrateWith: 'dehydrateResource')]
    public ?ResourceInterface $cart = null;

    /** @param OrderRepositoryInterface<OrderInterface> $orderRepository */
    public function __construct(OrderRepositoryInterface $orderRepository)
    {
        $this->initialize($orderRepository);
    }

    #[LiveListener(FormComponent::SYLIUS_SHOP_CART_CHANGED)]
    public function refreshCart(#[LiveArg] mixed $cartId): void
    {
        // Fix: ignore user-supplied cartId, reload from checksummed cart prop
        if ($this->cart === null) {
            return;
        }

        $this->cart = $this->hydrateResource($this->cart->getId());
    }
}

Step 5. Register overridden services

In config/services.yaml, add:

yaml
    sylius_shop.twig.component.checkout.address.form:
        class: App\Twig\Components\Checkout\Address\FormComponent
        arguments:
            $repository: '@sylius.repository.order'
            $formFactory: '@form.factory'
            $resourceClass: '%sylius.model.order.class%'
            $formClass: 'Sylius\Bundle\ShopBundle\Form\Type\Checkout\AddressType'
            $customerContext: '@sylius.context.customer'
            $shopUserRepository: '@sylius.repository.shop_user'
            $addressRepository: '@sylius.repository.address'
        tags:
            - { name: 'sylius.live_component.shop', key: 'sylius_shop:checkout:address:form' }

    sylius_shop.twig.component.cart.widget:
        class: App\Twig\Components\Cart\WidgetComponent
        arguments:
            $cartContext: '@sylius.context.cart.composite'
            $orderRepository: '@sylius.repository.order'
        tags:
            - { name: 'sylius.live_component.shop', key: 'sylius_shop:cart:widget' }

    sylius_shop.twig.component.cart.summary:
        class: App\Twig\Components\Cart\SummaryComponent
        arguments:
            $orderRepository: '@sylius.repository.order'
        tags:
            - { name: 'sylius.live_component.shop', key: 'sylius_shop:cart:summary' }

Step 6. Clear cache

bash
php bin/console cache:clear

Reporters

We would like to extend our gratitude to the following individuals for their detailed reporting and responsible disclosure of this vulnerability:

For more information

If you have any questions or comments about this advisory:

Software afectado

Haz clic en una tecnología para ver todas sus vulnerabilidades.

PlataformaPaqueteVersiones vulnerablesCorregida en
Paquete de Composer sylius/sylius >= 2.0.0, < 2.0.16, >= 2.1.0, < 2.1.12, >= 2.2.0, < 2.2.3 2.0.16, 2.1.12, 2.2.3

Referencias

Puntuación CVSS -
Vector CVSS
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
Debilidad (CWE)
CWE-639
Plataforma
Composer
Paquete
sylius/sylius
Publicada
11.03.2026
Actualizado
11.10.2026 10:22

Fuentes y licencias

OSV / GitHub Advisory Database - GHSA-2xc6-348p-c2x6 - CC BY 4.0

Historial de vulnerabilidades: sylius/sylius

Todas las vulnerabilidades publicadas de este paquete (Paquete de Composer), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.

Todas (23) en el archivo
Entradas en total
23
Primera → última
12.2019 07.2026
Sin corrección
0
Explotada en ataques
0

Distribución por gravedad

  • Alta 4
  • Media 17
  • Baja 2

Divulgaciones por año

  1. 2019: 1
  2. 2020: 3
  3. 2021: 1
  4. 2022: 4
  5. 2023: 0
  6. 2024: 4
  7. 2025: 0
  8. 2026: 10
Gravedad:
  1. Alta · 8.2 CVE-2026-31824 < 1.9.12, >= 1.10.0, < 1.10.16, >= 1.11.0, < 1.11.17, >= 1.12.0, < 1.12.23, >= 1.13.0, < 1.13.15, >= 1.14.0, < 1.14.18, >= 2.0.0, < 2.0.16, >= 2.1.0, < 2.1.12, >= 2.2.0, < 2.2.3 1.9.12, 1.10.16, 1.11.17, 1.12.23, 1.13.15, 1.14.18, 2.0.16, 2.1.12, 2.2.3
  2. Sylius affected by IDOR in Cart and Checkout LiveComponents esta entrada
    Alta CVE-2026-31820 >= 2.0.0, < 2.0.16, >= 2.1.0, < 2.1.12, >= 2.2.0, < 2.2.3 2.0.16, 2.1.12, 2.2.3
  3. Alta · 7.5 CVE-2024-40633 >= 1.12.0-alpha.1, < 1.12.19, >= 1.13.0-alpha.1, < 1.13.4, < 1.9.12, >= 1.10.0-alpha.1, < 1.10.16, >= 1.11.0-alpha.1, < 1.11.17 1.12.19, 1.13.4, 1.9.12, 1.10.16, 1.11.17
  4. Alta · 7.1 CVE-2022-24743 >= 1.10.0, < 1.10.11, >= 1.11.0, < 1.11.2 1.10.11, 1.11.2

Identifica las vulnerabilidades de tu aplicación

Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.

Conectar tu aplicación
Newsletter DockRay

La seguridad de tus aplicaciones en tu correo

Vulnerabilidades importantes, recomendaciones concretas y consejos prácticos para tus aplicaciones. Elige tus tecnologías y sigue lo que importa para tu proyecto.

  • Un resumen diario de nuevas vulnerabilidades, también las críticas.
  • Avisos sobre actualizaciones importantes.
  • Consejos prácticos sobre seguridad y monitorización de aplicaciones.

Déjalo vacío para recibir avisos de todas las tecnologías.

Suscripción gratuita. Date de baja con un clic en cualquier correo.