CVE-2025-30152: Sylius PayPal Plugin has an Order Manipulation Vulnerability after PayPal Checkout
A discovered vulnerability allows users to modify their shopping cart after completing the PayPal Checkout process and payment authorization. If a user initiates a PayPal transaction from a product page or the cart page and then returns to the order summary page, they can still manipulate the cart contents before finalizing the order. As a result, the order amount in Sylius may be higher than the amount actually captured by PayPal, leading to a scenario where merchants deliver products or services without full payment.
Impact
- Users can exploit this flaw to receive products/services without paying the full amount. - Merchants may suffer financial losses due to underpaid orders. - Trust in the integrity of the payment process is compromised.
Patches
The issue is fixed in versions: 1.6.2, 1.7.2, 2.0.2 and above.
Workarounds
To resolve the problem in the end application without updating to the newest patches, there is a need to overwrite PayPalOrderCompleteProcessor with modified logic:
php <?php
declare(stricttypes=1);
namespace App\Processor;
use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\PayPalPlugin\Manager\PaymentStateManagerInterface;
final class PayPalOrderCompleteProcessor { public function construct(private readonly PaymentStateManagerInterface $paymentStateManager) { }
public function completePayPalOrder(OrderInterface $order): void { $payment = $order->getLastPayment(PaymentInterface::STATEPROCESSING); if ($payment === null) { return; }
/ @var PaymentMethodInterface $paymentMethod / $paymentMethod = $payment->getMethod(); / @var GatewayConfigInterface $gatewayConfig / $gatewayConfig = $paymentMethod->getGatewayConfig();
if ($gatewayConfig->getFactoryName() !== 'sylius.paypal') { return; }
try { $this->verify($payment); } catch (\Exception) { $this->paymentStateManager->cancel($payment);
return; }
$this->paymentStateManager->complete($payment); }
private function verify(PaymentInterface $payment): void { $totalAmount = $this->getTotalPaymentAmountFromPaypal($payment);
if ($payment->getOrder()->getTotal() !== $totalAmount) { throw new \Exception(); } }
private function getTotalPaymentAmountFromPaypal(PaymentInterface $payment): int { $details = $payment->getDetails();
return $details['paymentamount'] ?? 0; } }
IMPORTANT
For PayPalPlugin 2.x change: php $gatewayConfig->getFactoryName() !== 'sylius.paypal' to php $gatewayConfig->getFactoryName() !== SyliusPayPalExtension::PAYPALFACTORYNAME
Also there is a need to overwrite CompletePayPalOrderListener with modified logic:
php <?php
declare(stricttypes=1);
namespace App\EventListener\Workflow;
use App\Processor\PayPalOrderCompleteProcessor; use Sylius\Component\Core\Model\OrderInterface; use Symfony\Component\Workflow\Event\CompletedEvent; use Webmozart\Assert\Assert;
final class CompletePayPalOrderListener { public function construct(private readonly PayPalOrderCompleteProcessor $completeProcessor) { }
public function invoke(CompletedEvent $event): void { / @var OrderInterface $order / $order = $event->getSubject(); Assert::isInstanceOf($order, OrderInterface::class);
$this->completeProcessor->completePayPalOrder($order); } }
And to overwrite CaptureAction with modified logic (if you didn't have it already):
php <?php
declare(stricttypes=1);
namespace App\Payum\Action;
use Payum\Core\Action\ActionInterface; use Payum\Core\Exception\RequestNotSupportedException; use Payum\Core\Request\Capture; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; use Sylius\PayPalPlugin\Api\CacheAuthorizeClientApiInterface; use Sylius\PayPalPlugin\Api\CreateOrderApiInterface; use Sylius\PayPalPlugin\Payum\Action\StatusAction; use Sylius\PayPalPlugin\Provider\UuidProviderInterface;
final class CaptureAction implements ActionInterface { public function construct( private CacheAuthorizeClientApiInterface $authorizeClientApi, private CreateOrderApiInterface $createOrderApi, private UuidProviderInterface $uuidProvider, ) { }
/ @param Capture $request / public function execute($request): void { RequestNotSupportedException::assertSupports($this, $request);
/ @var PaymentInterface $payment / $payment = $request->getModel(); / @var PaymentMethodInterface $paymentMethod / $paymentMethod = $payment->getMethod();
$token = $this->authorizeClientApi->authorize($paymentMethod);
$referenceId = $this->uuidProvider->provide(); $content = $this->createOrderApi->create($token, $payment, $referenceId);
if ($content['status'] === 'CREATED') { $payment->setDetails([ 'status' => StatusAction::STATUSCAPTURED, 'paypalorderid' => $content['id'], 'referenceid' => $referenceId, 'paymentamount' => $payment->getAmount(), ]); } }
public function supports($request): bool { return $request instanceof Capture && $request->getModel() instanceof PaymentInterface ; } }
After that, register services in the container when using PayPal 1.x:
yaml Sylius\PayPalPlugin\EventListener\Workflow\CompletePayPalOrderListener: class: App\EventListener\Workflow\CompletePayPalOrderListener public: true arguments: - '@Sylius\PayPalPlugin\Processor\PayPalOrderCompleteProcessor' tags: - { name: 'kernel.eventlistener', event: 'workflow.syliusordercheckout.completed.complete', priority: 100 } Sylius\PayPalPlugin\Processor\PayPalOrderCompleteProcessor: class: App\Processor\PayPalOrderCompleteProcessor public: true arguments: - '@Sylius\PayPalPlugin\Manager\PaymentStateManagerInterface'
Sylius\PayPalPlugin\Payum\Action\CaptureAction: class: App\Payum\Action\CaptureAction public: true arguments: - '@Sylius\PayPalPlugin\Api\CacheAuthorizeClientApiInterface' - '@Sylius\PayPalPlugin\Api\CreateOrderApiInterface' - '@Sylius\PayPalPlugin\Provider\UuidProviderInterface' tags: - { name: 'payum.action', factory: 'sylius.paypal', alias: 'payum.action.capture' }
or when using PayPal 2.x:
yaml syliuspaypal.listener.workflow.completepaypalorder: class: App\EventListener\Workflow\CompletePayPalOrderListener public: true arguments: - '@syliuspaypal.processor.paypalordercomplete' tags: - { name: 'kernel.eventlistener', event: 'workflow.syliusordercheckout.completed.complete', priority: 100 } syliuspaypal.processor.paypalordercomplete: class: App\Processor\PayPalOrderCompleteProcessor public: true arguments: - '@syliuspaypal.manager.paymentstate'
syliuspaypal.payum.action.capture: class: App\Payum\Action\CaptureAction public: true arguments: - '@syliuspaypal.api.cacheauthorizeclient' - '@syliuspaypal.api.createorder' - '@syliuspaypal.provider.uuid' tags: - { name: 'payum.action', factory: 'sylius.paypal', alias: 'payum.action.capture' }
For more information
If you have any questions or comments about this advisory: Open an issue in Sylius issues Email us at security@sylius.com
Other sources
The Syliud PayPal Plugin is the Sylius Core Team’s plugin for the PayPal Commerce Platform. Prior to 1.6.2, 1.7.2, and 2.0.2, a discovered vulnerability allows users to modify their shopping cart after completing the PayPal Checkout process and payment authorization. If a user initiates a PayPal transaction from a product page or the cart page and then returns to the order summary page, they can still manipulate the cart contents before finalizing the order. As a result, the order amount in Sylius may be higher than the amount actually captured by PayPal, leading to a scenario where merchants deliver products or services without full payment. The issue is fixed in versions: 1.6.2, 1.7.2, 2.0.2 and above.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/sylius/paypal-pluginto a version that resolves this vulnerability.Fixed in 2.0.2 - Upgrade
Upgrade
composer/sylius/paypal-pluginto a version that resolves this vulnerability.Fixed in 1.7.2 - Upgrade
Upgrade
composer/sylius/paypal-pluginto a version that resolves this vulnerability.Fixed in 1.6.2 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 1.6.2 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 1.7.2 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 2.0.2 - Configuration
In the end application, register container services so that `App\EventListener\Workflow\CompletePayPalOrderListener`, `App\Payum\Action\CaptureAction`, and `App\Processor\PayPalOrderCompleteProcessor` are used instead of the plugin defaults; apply the modified logic shown (including the amount verification `if ($payment->getOrder()->getTotal() !== $totalAmount)`).
PayPalPlugin (Sylius PayPal Plugin) - service container registration for PayPal 1.x service override / wiring = Overwrite `CompletePayPalOrderListener` with modified logic, and overwrite `CaptureAction` and `PayPalOrderCompleteProcessor` if needed (register the overridden services in the container for PayPal 1.x).
Event History
Frequently Asked Questions
What is the severity of CVE-2025-30152?
CVE-2025-30152 is classified as a critical vulnerability due to the potential for unauthorized modifications to a user's shopping cart after payment.
How do I fix CVE-2025-30152?
To fix CVE-2025-30152, upgrade the Sylius PayPal Plugin to version 1.6.2, 1.7.2, or 2.0.2 or later.
What software is affected by CVE-2025-30152?
CVE-2025-30152 affects the Sylius PayPal Plugin versions prior to 1.6.2, 1.7.2, and 2.0.2.
What impact does CVE-2025-30152 have on users?
CVE-2025-30152 allows users to potentially alter their shopping cart after completing a PayPal transaction, leading to unauthorized orders.
Is there a known exploit for CVE-2025-30152?
Yes, CVE-2025-30152 has been identified as allowing exploitation scenarios regarding cart modifications post-payment, posing security risks.