Skip to content
winfunc
Disclosure record · Sylius

Captured payment can be reused after cart total inflation

Cart recalculation rewrote a payment already claimed by the gateway

SyliusDisclosedSource record

CVSS 3.1 base score

High
7.5/ 10

Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

Record

Project
Sylius
Severity
High7.5
CVE
None listed
Disclosed
Trace
3 steps

Summary

Source

AddPaymentRequest::__construct()

src/Sylius/Bundle/ApiBundle/Command/Payment/AddPaymentRequest.php:25

Sink

CapturePaymentAction::execute()

src/Sylius/Bundle/PayumBundle/Action/CapturePaymentAction.php:70

A guest could capture a legitimate low amount, enlarge the same mutable order, and have Sylius mark the enlarged order paid while the payment provider had captured only the original amount.

Root cause. OrderPaymentProcessor rewrote the amount and currency of the last cart-state payment whenever the order total changed, even after a capture or authorization request had handed that payment to a gateway. The later status flow trusted the gateway's success state but did not reconcile the captured amount.

Remediation evidence. Sylius published GHSA-vv4h-q2x8-74g4 with an explicit Winfunc Research credit and fixed the invariant in releases 2.1.16 and 2.2.9 by preventing claimed payments from being rewritten or removed.

Severity

Attack vectorAV
Network
Attack complexityAC
Low
Privileges requiredPR
None
User interactionUI
None
ScopeS
Unchanged
ConfidentialityC
None
IntegrityI
High
AvailabilityA
None

Metric values as published in the disclosure vector. Meters show how far each value raises exposure.

Source-to-sink trace

  1. Source · attacker-controlledsrc/Sylius/Bundle/ApiBundle/Command/Payment/AddPaymentRequest.php:25

    AddPaymentRequest::__construct()

  2. Step 01src/Sylius/Bundle/ApiBundle/Resources/config/api_platform/resources/shop/PaymentRequest.xml:37

    A guest starts a gateway capture for an inexpensive, attacker-owned cart payment.

    python
    POST /api/v2/shop/orders/{token}/payment-requests
    { "paymentId": 7, "action": "capture" }
    
  3. Step 02src/Sylius/Component/Core/OrderProcessing/OrderPaymentProcessor.php

    After a successful low-value capture, cart recalculation overwrites the same local payment with the enlarged order total.

    php
    $payment->setAmount($order->getTotal());
    $payment->setCurrencyCode($order->getCurrencyCode());
    
  4. Step 03src/Sylius/Bundle/PayumBundle/Action/StatusPaymentAction.php

    The gateway reports the earlier transaction captured, and Sylius completes the rewritten payment without comparing the provider amount.

    php
    if ($status->isCaptured()) {
      $stateMachine->apply($payment, 'complete');
    }
    
  5. Sinksrc/Sylius/Bundle/PayumBundle/Action/CapturePaymentAction.php:70

    CapturePaymentAction::execute()

Impact

Reported impact

The store can fulfill goods or other paid value worth more than the provider actually captured. The confirmed impact is payment/order integrity failure and underpayment fraud; service availability and data confidentiality are unaffected.

Attack surface

Guest or customer checkout on Sylius 2.x stores using a capture-capable Payum gateway and the Payment Request API.

Preconditions

The attacker controls their own cart and a valid low-value payment instrument. The merchant must have a capture/status-capable gateway enabled; no victim account or callback forgery is needed.

Attack path

  1. 1

    Create an inexpensive cart and start a legitimate gateway capture.

  2. 2

    Complete capture for the original low amount.

  3. 3

    Increase the quantity or otherwise raise the same mutable order total.

  4. 4

    Complete checkout and request payment status.

  5. 5

    Sylius marks the enlarged order paid using the rewritten local amount.

Proof of concept

Reproduction5 stages
  1. 01

    Environment setup

    Use an isolated affected Sylius 2.x store with a capture-capable test Payum gateway and one inexpensive product.

  2. 02

    Target configuration

    Enable the shop Payment Request API and normal guest checkout. Use provider test credentials only.

  3. 03

    Exploit delivery

    Capture the initial cart total, increase item quantity on the same cart, complete checkout, then process a status request for the original gateway transaction.

  4. 04

    Expected response

    The order reaches paid for the enlarged total while the gateway ledger shows only the original smaller capture.

  5. 05

    Outcome

    In 2.1.16 and 2.2.9, payments already claimed by a gateway are not rewritten; the enlarged order requires a distinct correctly valued payment.

Remediation

Guidance

Upgrade to Sylius 2.1.16, 2.2.9, or later. Treat authorization/capture as an immutable claim on a specific payment amount and currency, and reconcile provider amount before paid-order transition.

Before and after−1+4
$payment->setAmount($order->getTotal());if ($this->isClaimedByGateway($payment)) {  $order->addPayment($this->orderPaymentProvider->provideOrderPayment($order, $targetState));  return;}
View original source record

Check the upstream record for the project's current remediation status.

Your codebase

Investigate the paths that matter in your codebase.

Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.