Captured payment can be reused after cart total inflation
Cart recalculation rewrote a payment already claimed by the gateway
CVSS 3.1 base score
HighVector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
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
- Source · attacker-controlledsrc/
Sylius/ Bundle/ ApiBundle/ Command/ Payment/ AddPaymentRequest.php:25 AddPaymentRequest::__construct()
- 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" } - 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()); - 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'); } - 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
Create an inexpensive cart and start a legitimate gateway capture.
- 2
Complete capture for the original low amount.
- 3
Increase the quantity or otherwise raise the same mutable order total.
- 4
Complete checkout and request payment status.
- 5
Sylius marks the enlarged order paid using the rewritten local amount.
Proof of concept
- 01
Environment setup
Use an isolated affected Sylius 2.x store with a capture-capable test Payum gateway and one inexpensive product.
- 02
Target configuration
Enable the shop Payment Request API and normal guest checkout. Use provider test credentials only.
- 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.
- 04
Expected response
The order reaches
paidfor the enlarged total while the gateway ledger shows only the original smaller capture. - 05
Outcome
In
2.1.16and2.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.
$payment->setAmount($order->getTotal());if ($this->isClaimedByGateway($payment)) { $order->addPayment($this->orderPaymentProvider->provideOrderPayment($order, $targetState)); return;}
Check the upstream record for the project's current remediation status.
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.
