| Titel | beikeshop BeikeShop <= 2.0.0.31 Improper Enforcement of Behavioral Workflow |
|---|
| Beschreibung | Reporter / requested credit: Fernando Viana - Capivara Research
GitHub reporter: capivara-research
SUMMARY
BeikeShop through x.x.x.x contains a payment workflow vulnerability in the default PayPal REST capture flow. An unauthenticated attacker can submit a valid, low-value PayPal transaction identifier together with an unrelated BeikeShop order number. The application accepts the PayPal response when its status is COMPLETED, but does not verify that the captured amount, currency, or PayPal order belongs to the local order being marked paid.
The issue is compounded by an insecure direct object reference in guest order lookup. When no customer is authenticated, OrderRepo::getOrderByNumber() does not apply a customer_id restriction. Consequently, a guest request can resolve a different customer's order if its order number is known or discovered.
AFFECTED COMPONENT AND VERSION
Product: BeikeShop
Affected versions: <= x.x.x.x
Audited revision: f41f390 (tag v2.0.0.31, 2026-08-28)
Plugin: PayPal v1.1.0
Endpoint: POST /paypal/capture
Authentication required: No (guest checkout is supported)
ROOT CAUSE
1. plugins/Paypal/Controllers/PaypalController.php::capture() accepts paypalOrderId and order_number from the request.
2. The REST branch calls capturePaymentOrder(paypalOrderId).
3. markOrderPaid() relies on PaypalService::isPaymentSuccessful().
4. isPaymentSuccessful() only tests whether the PayPal response status equals COMPLETED. It does not compare the captured amount or currency with the local order total, and does not establish that paypalOrderId was created for order_number.
5. beike/Repositories/OrderRepo.php::getOrderByNumber() adds a customer_id condition only when a logged-in customer exists. For a guest request, the lookup can resolve an order regardless of ownership.
The repository itself demonstrates the missing control: the sibling NVP/SOAP flow implements validateNvpDetails(), which compares PayPal amount, currency, and invoice data with the local order. Equivalent validation is absent from the default REST flow.
ATTACK SCENARIO / REPRODUCTION LOGIC
1. The attacker creates a cheap order and starts the normal PayPal REST checkout using POST /paypal/create.
2. The attacker approves and pays that low-value PayPal order (for example, USD 1.00), obtaining a real paypalOrderId.
3. The attacker sends POST /paypal/capture with the real low-value paypalOrderId, but supplies the order_number of a different, more expensive order.
4. PayPal reports the attacker's genuine low-value transaction as COMPLETED.
5. BeikeShop marks the unrelated target order PAID because it checks only the completion status, not amount, currency, invoice/order binding, or guest ownership.
The target may be the attacker's own expensive order or another customer's order. Order numbers are generated from a date prefix plus a five-digit rand()-based value, which also makes enumeration or guessing more feasible than possession of a cryptographically strong per-order secret.
IMPACT
An unauthenticated remote attacker can pay an arbitrary lower amount and cause an unrelated, higher-value order to be treated as fully paid. This can lead to unauthorized fulfillment of goods or services, direct financial loss, order-state integrity compromise, and manipulation of other customers' orders.
CLASSIFICATION AND SEVERITY
Severity: High
CWE-841: Improper Enforcement of Behavioral Workflow
CWE-284: Improper Access Control
Suggested CVSS v3.1: 8.2 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N)
VALIDATION STATUS
The chain was validated by static source review of the affected revision. It was not exercised against a production PayPal merchant account, because that would require merchant credentials and real fund movement. The relevant code path deterministically accepts a response based solely on status === COMPLETED, without inspecting the payment amount or currency. The absence of the guest customer_id filter and server-side PayPal-to-local-order binding is also directly visible in the source.
REMEDIATION
1. Retrieve and validate PayPal REST order/capture details server-side. Compare captured amount and currency with the authoritative local order total and configured store currency before changing order state.
2. Bind paypalOrderId to the local order at creation time using server-side state and suitable PayPal metadata such as custom_id or invoice_id. Reject any capture whose binding does not match.
3. Require a cryptographically strong, per-order guest token/session for guest order operations. A plaintext order number must not act as authorization.
4. Make payment-state updates atomic and idempotent, and reject replayed or previously associated PayPal transaction identifiers.
5. Add regression tests for mismatched amount, currency, local order number, guest ownership, and replay.
DUPLICATE CHECK / DISTINCTION
This is not VulDB VDB-369082 (Submit 831466). That entry concerns a missing Stripe webhook signature check in /callback/stripe and affects BeikeShop through 1.6.0.22. This report concerns the PayPal REST capture flow through x.x.x.x: missing amount/currency and transaction-to-order binding validation, combined with guest order IDOR.
COORDINATED DISCLOSURE EVIDENCE
The issue was privately reported to the vendor through GitHub Security Advisories as GHSA-p2x8-f9cm-m352. It remains a private draft/triage advisory and has no CVE assigned. The attached screenshot shows the private advisory, affected version, severity, reporter attribution, and vendor repository context.
Credit requested in the VulDB entry and CVE record: Fernando Viana - Capivara Research.
|
|---|
| Quelle | ⚠️ https://github.com/beikeshop/beikeshop/security/advisories/GHSA-p2x8-f9cm-m352 |
|---|
| Benutzer | fernando viana (UID 101484) |
|---|
| Einreichung | 16.10.2026 00:55 (vor 24 Tagen) |
|---|
| Moderieren | 08.11.2026 17:59 (24 days later) |
|---|
| Status | Akzeptiert |
|---|
| VulDB Eintrag | 432922 [BeikeShop bis 2.0.0.31 Guest Order Lookup OrderRepo.php getOrderByNumber order_number erweiterte Rechte] |
|---|
| Punkte | 20 |
|---|