Payment-gateway webhook branch integrity and client-trusted amount state (Sept 23 WP/WC cluster)¶
Source: hourly offensive-security scan, 2026-09-23. GitHub advisories GHSA-wv8r-wv6j-wh9g / CVE-2026-93511 (Premium Packages), GHSA-7x4m-8ff6-56c9 / CVE-2026-87979 + GHSA-pgxh-7vg6-g5g2 / CVE-2026-87981 (Paymob for WooCommerce), GHSA-68x3-xpqw-vw3q / CVE-2026-80342 (Payment Plugins for PayPal WooCommerce), GHSA-h7x3-7cw4-64m2 / CVE-2026-77765 (Better Payment), GHSA-8v3j-283x-6xf7 / CVE-2026-93510 (Points and Rewards for WooCommerce).
Five money-handling WordPress/WooCommerce plugins failed the same way in one publication window: the gateway's cryptographic or server-side integrity control exists, but it is applied per-branch, per-state, or not at all — and the skipped branch is the exploitable one. This generalizes the Sept 22 ZenHive challenge-binding rule (verifier state vs dedup state) to the payment-gateway integration layer, and it is a coherent audit checklist for any WooCommerce payment extension or webhook-consuming SaaS.
The cluster¶
| Plugin | GHSA / CVE | Boundary |
|---|---|---|
| Premium Packages <7.2.1 | GHSA-wv8r-wv6j-wh9g / CVE-2026-93511 | PayPal webhook signature is never verified before processing payment and subscription-cancellation notifications → unauthenticated attacker forges payment confirmations and cancellations against any order whose transaction id they know. Knowledge of a transaction id is the authorization once the signature check is absent. |
| Paymob for WooCommerce <4.1.14 | GHSA-7x4m-8ff6-56c9 / CVE-2026-87979 | Webhook signature is verified on some branches but not the card-token branch → unauthenticated write of a card-token record onto any user's account, plus registered-account enumeration. Signed and unsigned branches coexist in one handler. |
| Paymob for WooCommerce <4.1.14 | GHSA-pgxh-7vg6-g5g2 / CVE-2026-87981 | Admin AJAX actions managing the payment-gateway configuration (incl. stored payment credentials) lack capability checks → Contributor deletes/wipes/modifies gateway config. Same plugin, config-plane twin of the webhook bug (the Aug-5-2026 *_api/api_key options-sweep axis, again). |
| Payment Plugins for PayPal WooCommerce <2.0.27 | GHSA-68x3-xpqw-vw3q / CVE-2026-80342 | The "does this PayPal order belong to this WooCommerce order?" check runs only when the PayPal order is already completed → an attacker submits a victim's approved-but-uncaptured PayPal order id on their own checkout and causes capture of the victim's authorized funds against the attacker's order. The condition that skips the integrity check is the vulnerability. |
| Better Payment ≤2.3.4 | GHSA-h7x3-7cw4-64m2 / CVE-2026-77765 | Submitted payment amount is never re-derived server-side against the merchant's fixed price → unauthenticated buyers pay any reduced amount for fixed-price items. |
| Points and Rewards for WooCommerce <2.10.4 | GHSA-8v3j-283x-6xf7 / CVE-2026-93510 | Win-Wheel claim handler validates neither the claimed reward amount nor who may call it → any Subscriber credits their own account with arbitrary, unlimited loyalty points (and wallet balance where the companion plugin is installed). |
Durable axes¶
- Webhook signature verification is per-branch coverage, not a global property. Paymob proves a single webhook handler can verify signatures on the payment branch and skip it on the card-token branch; Premium Packages proves the whole handler can skip it. Audit rule: for every webhook consumer, enumerate the event-type/branch dispatch table (payment, cancellation, refund, card-token, subscription, dispute) and test each branch independently with a forged, correctly-shaped-but-unsigned payload. A signed branch returning 401 on tamper tells you nothing about the sibling branch. This is the per-tool/per-auth-branch coverage-drift class (mcp-atlassian unhooked-session legs, Sept 22) wearing a payments costume.
- Conditional integrity checks: probe the skip-condition. Payment Plugins' ownership check runs only for already-completed PayPal orders — every
if (state == X) verify()pattern hides an unverified non-X path. For any cross-object binding (order↔order, intent↔cart, voucher↔purchase), enumerate the state machine's states and run the foreign-object-id submission in each state; the approved-uncaptured window is exactly the state a developer assumes "can't happen yet." Funded-but-uncaptured payment instruments are a reusable capture primitive when the binding check defers to completion. - Amounts, rewards, and credit are server-derived or they are attacker-derived. Better Payment (amount), Points and Rewards (claimed reward + caller identity) both trust the client number. Sweep every checkout/claim/redeem endpoint with a modified amount/value parameter and diff the recorded ledger against the server-side expected value; the float-cast/numeric-validator gap (Sept 22) is the input-shape sibling — here the validator is absent, not weak.
- Identifiers become credentials when verification is absent. Premium Packages' exploitability precondition is "knows the transaction id." Transaction ids, order keys, invoice numbers, and intent ids appear in confirmation pages, emails, REST responses, and are often sequential. Treat every opaque id used at an unverified endpoint as an enumerable credential: harvest your own ids first, measure their structure (sequential? date-prefixed? hash?), and report the structure — sequential ids convert a signature-missing bug from targeted to fleet-scale.
- Payment config planes inherit the plugin's weak auth posture. Paymob's contributor-reachable gateway-config wipe is the third-plus instance (after WP User Manager Stripe connect, Handily, Image Buzz/PixelPlay on Sept 22) that gateway credentials are just options rows. The sweep is canonical: parameter names
[a-z]+_api|api_key|secret_key|client_key|server_key|webhook× lowest authenticated principal × write/delete verbs.
Validation workflow (authorized scope only)¶
Lab stores, gateway sandbox/test accounts, synthetic money
Run every leg on a WooCommerce lab install with gateway sandbox/test-mode accounts you own. Never probe a production gateway webhook with forged signatures (that is an attack, and gateway providers log it), never use real transaction ids or real buyer instruments, and never submit foreign order/intent ids outside your own lab tenant.
- Branch coverage matrix (webhook consumers): on the lab store, complete one real sandbox transaction; capture the webhook payload shape. For each event-type branch, resend the payload (a) untampered, (b) amount/status tampered without signature, (c) tampered with an invalid signature. Record per-branch accept/reject — a single accepting branch is the finding. Evidence = order/subscription state transitions in your lab DB caused by (b).
- Cross-order capture proof: two lab buyer accounts, sandbox PayPal. Buyer A approves (does not capture) an order. On Buyer B's lab checkout, replay A's PayPal order id in the payment-request parameter. Vulnerable evidence: A's authorization is captured and B's order marked paid while A's cart shows captured. Immediately refund/void in sandbox and record ids.
- Amount re-derivation: add a fixed-price lab product; submit the payment request with the amount parameter rewritten to 1 (or the currency minimum). Evidence = captured sandbox charge amount vs catalog price diff.
- Reward-claim sweep: call the claim/redeem handler directly (bypass the UI) with arbitrary reward values and from a second lab user; ledger diff is the evidence. Test the handler with the UI-gating precondition (wheel spin state) absent — call-time identity checks are what you're proving missing.
- Config-plane check: as a Subscriber/Contributor lab user, attempt the plugin's admin AJAX config actions (delete/update gateway keys) with your own synthetic test keys as the payload. Positive/negative pair: contributor write accepted vs admin-only control.
Reporting heuristics¶
- Lead with the money movement, not the missing check: "attacker captures another buyer's authorized funds" (80342) and "unauthenticated forged payment confirmation for any known transaction id" (93511) are the headline sentences; "missing signature verification" is the root cause line.
- State the precondition honestly: signature-missing bugs need a transaction id (report its structure and enumerability); cross-order capture needs the approved-uncaptured window (report the provider state you exploited).
- Report branch coverage explicitly: attach the per-event-type accept/reject table — it both proves the finding and tells the vendor which branches were unverified, which prevents an incomplete-fix repeat (the Directorist/CVE-2023-1889 pattern in this same wave).
- Keep sandbox evidence: order ids, ledger diffs, state-transition logs. Redact gateway account ids and buyer emails even for synthetic buyers.
October 2 follow-up: cookie-minting payment callbacks and post-activation fail-open windows (Oct 2 06:30Z WP wave, folded)¶
- Divi Membership ≤2.3.0 unauthenticated full site takeover via
process_paypal_callback(GHSA-fwjj-2936-g23h, 9.8): the callback, hooked toinit, accepts a base64-encodedpaypal_paramGET parameter with no IPN verification, no signature, no nonce, no ownership check and passes the decoded attacker-chosen user ID straight towp_set_current_user()+wp_set_auth_cookie()→ login as any user, admins included. Compounding detail: the PayPal gateway class is instantiated unconditionally regardless of whether PayPal is enabled or configured — the "disabled gateway" assumption is false at the routing level. Durable axes: (1) payment callbacks that hand out sessions treat a user ID as a credential — any callback param that reacheswp_set_auth_cookie()/wp_set_current_user()is an auth-bypass candidate; decode the param envelope (base64 is the WordPress callback convention) and swap the identity field first; (2) feature-gate ≠ route-gate — a gateway toggle in settings gates the workflow, not the hook registration; enumerate payment callback routes (init-hooked handlers taking provider-named params) even on stores where that gateway is visibly off, and prove with a lab account's own user ID. - Paytm Payment Gateway <2.8.9 unauthenticated order-status forging in the unconfigured state (GHSA-2xw5-r48v-m7fg): when the merchant secret key is unset — the state immediately after activation, before configuration completes — the callback skips origin verification entirely and lets anonymous callers mark any unpaid order paid and drain stock. Durable axis: post-activation default state is a fail-open authentication window. Verify-iff-configured patterns (
if (secret) validate()) invert into no-auth zones during install/reinstall/onboarding; on bug-bounty scopes with freshly installed plugins (or on reinstalls after compromise cleanup), probe callbacks with the unsigned shape first, and in reports name the exact configuration state the skip-condition depends on. - Bounded validation: lab store with the plugin freshly activated (secret intentionally unset) and a synthetic unpaid order you own; forge the callback for your own order and diff order-state + stock transitions; delete synthetic data after.
October 5 follow-up: payment-confirmation endpoint with no order↔request binding (UPI QR Code Payment Gateway, folded)¶
- UPI QR Code Payment Gateway for WooCommerce through 1.4.3 (CVE-2026-84169 / GHSA-vjc6-47gr-qc78, medium): the payment-confirmation request is never bound to the order and customer it claims to confirm — an unauthenticated attacker marks any arbitrary order as paid without paying. Purest instance yet of this page's axis 1+4 combo: no signature branch to test at all, just a missing token-vs-target binding on the confirm verb.
- Operator note: UPI/QR gateway confirmations typically carry a payment reference + order id and are validated (if at all) by presence, not by ownership or amount re-derivation. Sweep on any QR/UPI/wallet gateway: take your own lab order's confirmation request shape, then replay it verbatim against a second lab order id owned by a second user — accept = finding. Same single-request proof generalizes to every "gateway told us so" endpoint where the gateway is a payment app the customer holds, because those callbacks routinely skip server-side reconciliation entirely.
Tracked without publication (same wave)¶
- GHSA-h6fw-4cwx-rhhr / CVE-2026-19438 (ABB Mint Workbench I path traversal through 5876) — industrial HMI engineering tool, zero technical detail; revisit if a sink path lands.
- GHSA-v73c-8gwm-q3w7 / CVE-2026-14321 (divi-dash spoofed client-IP for rate limiting/banning) — forwarding-header trust axis already canonical on the Aug 28 9router page.
- GHSA-rxfw-g454-gj22 / CVE-2026-90951 (Paid Membership Subscriptions unbound payment-action deletes another member's checkout state) — token-vs-target binding family, low impact (DoS on checkout state).
- GHSA-fvx4-2974-ggpj / CVE-2026-88929, GHSA-cmc3-8j7j-94xr / CVE-2026-86602, GHSA-x59c-4q2g-w5j4 / CVE-2026-91077, GHSA-xjv5-f9gc-p6v3 / CVE-2026-84741 — non-public status-state disclosure quartet; the status-disclosure axis is already canonical (Motors, Sept 17).
- GHSA-8267-mm42-3259 / CVE-2026-86785 (Social Commerce for WooCommerce unauth REST config write) — missing-auth-on-REST axis, no cross-boundary novelty beyond config write.
- Stored/reflected XSS singles (MasterStudy 81338 stored HTML/iframe, Subscribe Forms 91073, JSM Show Post Metadata 88997 admin meta-key, HappyAddons 85006, Real3D Flipbook 86842/2025-15696, Auktion NG 96258) and enumeration/disclosure singles (zportals 18365 user-dump, FluentBoards 89331 shared-board member emails, Newsletters 16264 ownership) — generic sanitizer/enum axes already canonical.
- CVE-2022-4997 jet-form-builder-stripe-gateway — historical GHSA sync re-surfacing.