GitHub Advisoryadvisory · Packagist

GHSA-mr9r-h354-966r Sylius: IDOR on Shop Payment Request API endpoints

MEDIUM Published 2026-07-09 Modified 2026-07-09 Official page ↗
Aliases
CVE-2026-53639
Ecosystem
Packagist
Packages
sylius/sylius
Severity
Moderate
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
Weaknesses
CWE-639
Reviewed
2026-07-09

Affected packages

• Packagist sylius/sylius: >= 2.0.0, < 2.0.18 → fixed in 2.0.18 • Packagist sylius/sylius: >= 2.1.0, < 2.1.15 → fixed in 2.1.15 • Packagist sylius/sylius: >= 2.2.0, < 2.2.6 → fixed in 2.2.6

Description

Impact The `GET /api/v2/shop/payment-requests/{hash}` and `PUT /api/v2/shop/payment-requests/{hash}` endpoints look up the payment request solely by the hash from the URL. No ownership check is performed against the authenticated customer or the underlying order. An attacker who obtains a payment request hash can: • read the payment request and, through the `payment` IRI in the response, recover the underlying order's `tokenValue` (which itself grants access to the full order, items, addresses, customer email, totals); • update the payment request payload (e.g. `target_path`, `after_path`). These fields are used by the front-end controller to redirect the user after the payment, so an attacker can flip them to an attacker-controlled URL and intercept the buyer. The hash is a UUID, so it has to be obtained out-of-band (logs, shared links, referrer headers, a co-located client), but once it is known no other credential is required, neither authentication nor knowledge of the order token. The creation endpoint `POST /api/v2/shop/orders/{tokenValue}/payment-requests` shares the same flaw: it resolves the target order solely from the `tokenValue` in the URL without verifying that the caller owns the order. Patches The issue is fixed in versions: 2.0.18, 2.1.15, 2.2.6. Workarounds Until you can upgrade, apply the following workaround. It enforces ownership on the existing endpoints, so that: • an authenticated shop user may only access payment requests of their own orders; • an anonymous caller may only access payment requests of guest orders (the order's customer has no associated user account); • everyone else receives `404 Not Found`. Step 1. Add a query extension that filters the `GET` operation Create file `src/ApiPlatform/QueryExtension/PaymentRequestOwnershipExtension.php`: ```php sectionProvider->getSection() instanceof ShopApiSection) { return; } $rootAlias = $queryBuilder->getRootAliases()[0]; $paymentJoin = $queryNameGenerator->generateJoinAlias('payment'); $orderJoin = $queryNameGenerator->generateJoinAlias('order'); $customerJoin = $queryNameGenerator->generateJoinAlias('customer'); $userJoin = $queryNameGenerator->generateJoinAlias('user'); $createdByGuestParameterName = $queryNameGenerator->generateParameterName('createdByGuest'); $queryBuilder ->innerJoin(sprintf('%s.payment', $rootAlias), $paymentJoin) ->innerJoin(sprintf('%s.order', $paymentJoin), $orderJoin) ->leftJoin(sprintf('%s.customer', $orderJoin), $customerJoin) ->leftJoin(sprintf('%s.user', $customerJoin), $userJoin) ; $user = $this->userContext->getUser(); if ($user instanceof ShopUserInterface) { $customerParam = $queryNameGenerator->generateParameterName('customer'); $queryBuilder ->andWhere($queryBuilder->expr()->eq(sprintf('%s.customer', $orderJoin), sprintf(':%s', $customerParam))) ->setParameter($customerParam, $user->getCustomer()) ; return; } $queryBuilder ->andWhere( $queryBuilder->expr()->orX( $queryBuilder->expr()->isNull($userJoin), $queryBuilder->expr()->isNull(sprintf('%s.customer', $orderJoin)), $queryBuilder->expr()->andX( $queryBuilder->expr()->isNotNull($userJoin), $queryBuilder->expr()->eq(sprintf('%s.createdByGuest', $orderJoin), sprintf(':%s', $createdByGuestParameterName)), ), ), ) ->setParameter($createdByGuestParameterName, true) ; } } ``` Step 2. Decorate the `PUT` state provider Create file `src/ApiPlatform/StateProvider/PaymentRequestOwnershipProvider.php`: ```php */ final readonly class PaymentRequestOwnershipProvider implements ProviderInterface { /** @param ProviderInterface $inner */ public function __construct( private ProviderInterface $inner, private UserContextInterface $userContext, ) { } public function provide(Operation $operation, array $uriVariables = [], array $context = []): array|object|null { $paymentRequest = $this->inner->provide($operation, $uriVariables, $context); if (!$paymentRequest instanceof PaymentRequestInterface) { return $paymentRequest; } if (!$this->isAccessible($paymentRequest)) { return null; } return $paymentRequest; } private function isAccessible(PaymentRequestInterface $paymentRequest): bool { $payment = $paymentRequest->getPayment(); if (!$payment instanceof PaymentInterface) { return false; } $order = $payment->getOrder(); if (!$order instanceof OrderInterface) { return false; } $user = $this->userContext->getUser(); if ($user instanceof ShopUserInterface) { $customer = $user->getCustomer(); return $customer instanceof CustomerInterface && $order->getCustomer() === $customer; } $customer = $order->getCustomer(); return null === $customer || null === $customer->getUser() || $order->isCreatedByGuest(); } } ``` Step 3. Guard the `POST` creation endpoint with a command-bus middleware The `POST /api/v2/shop/orders/{tokenValue}/payment-requests` operation is a `messenger: input` operation: it dispatches a `Sylius\Bundle\ApiBundle\Command\Payment\AddPaymentRequest` command whose `orderTokenValue` comes straight from the URL, so no query extension or state provider runs. Add a middleware on the Sylius command bus that loads the order, applies the same ownership rule, and aborts with `404` before the handler runs. Create file `src/Messenger/Middleware/PaymentRequestOwnershipMiddleware.php`: ```php <?php declare(strict_types=1); namespace App\Messenger\Middleware; use Sylius\Bundle\ApiBundle\Command\Payment\AddPaymentRequest; use Sylius\Bundle\ApiBundle\Context\UserContextInterface; use Sylius\Component\Core\Mode…

Related

Alias · CVE (1)

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

Weakness · CWE (1)

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

References