WooCommerce Stripe Payment Gateway <= 10.7.0 - Missing Authorization to Unauthenticated Order Status Manipulation via 'order' Parameter
Description
The WooCommerce Stripe Payment Gateway plugin for WordPress is vulnerable to unauthorized modification of data due to a missing capability check on the `ajax_pay_for_order()` function in all versions up to, and including, 10.7.0 This is due to a missing order ownership or order_key verification when processing payment for an order via the `wc_stripe_pay_for_order` WC-AJAX endpoint. The function only validates a nonce (which is publicly available on any WooCommerce page where Express Checkout is enabled), but does not verify that the requesting user owns the target order and is allowed to modify it. This makes it possible for unauthenticated attackers to force any pending order into a failed status by providing a fake payment method, causing a payment exception that updates the order status to "failed" via sequential order ID enumeration.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:LTechnical Details
<=10.7.0What Changed in the Fix
Changes introduced in v10.8.0
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-2381 ## 1. Vulnerability Summary The **WooCommerce Stripe Payment Gateway** plugin (<= 10.7.0) contains a missing authorization vulnerability in its AJAX handler for order payments. Specifically, the function `ajax_pay_for_order()` (registered via the `wc_stri…
Show full research plan
Exploitation Research Plan: CVE-2026-2381
1. Vulnerability Summary
The WooCommerce Stripe Payment Gateway plugin (<= 10.7.0) contains a missing authorization vulnerability in its AJAX handler for order payments. Specifically, the function ajax_pay_for_order() (registered via the wc_stripe_pay_for_order WC-AJAX endpoint) fails to verify order ownership or validate the order_key.
While the function checks a WordPress nonce, this nonce is exposed to unauthenticated users on any page where Stripe Express Checkout is enabled. Consequently, an unauthenticated attacker can supply an arbitrary Order ID and a malformed payment method. This triggers a payment exception within the plugin's logic, which the plugin handles by automatically updating the status of the targeted order to "failed".
2. Attack Vector Analysis
- Endpoint:
[TARGET_URL]/?wc-ajax=wc_stripe_pay_for_order(Thewc-ajaxquery parameter triggers the WooCommerce AJAX handler). - Action Identifier:
wc_stripe_pay_for_order - Vulnerable Parameter:
order(Used to specify the target Order ID). - Authentication: None required (Unauthenticated).
- Preconditions:
- The targeted order must be in a "Pending Payment" status.
- The "Payment Request Buttons" (Express Checkout) must be enabled in Stripe settings to expose the required nonce.
3. Code Flow
- Entry Point: The request hits
admin-ajax.phporindex.phpwith?wc-ajax=wc_stripe_pay_for_order. - Hook Registration: The plugin registers the AJAX handler (typically in a class like
WC_Stripe_Payment_Gateway_Ajaxor similar):add_action( 'wc_ajax_wc_stripe_pay_for_order', array( $this, 'ajax_pay_for_order' ) ); - Handler Execution:
ajax_pay_for_order()is called. - Nonce Verification: The function calls
check_ajax_referer( 'wc_stripe_pay_for_order', '_wpnonce' ). - Missing Check (The Vulnerability): The function retrieves the order using
wc_get_order( $_POST['order'] ). Crucially, it does not verify that the current user is the owner of the order or that a validorder_key(secret token) was provided. - Payment Processing: The function attempts to process payment using the provided
payment_method. - Exception Handling: If the
payment_methodis invalid (e.g.,pi_invalid), the Stripe API client throws an exception. - Sink: The
catchblock inajax_pay_for_order()catches the exception and calls$order->update_status( 'failed', ... ), effectively modifying the order data without authorization.
4. Nonce Acquisition Strategy
The nonce for the wc_stripe_pay_for_order action is localized in the wc_stripe_params object when the Stripe scripts are enqueued.
- Identify Trigger: The nonce is enqueued on product pages or the cart page when "Payment Request Buttons" are active.
- Navigation: Use
browser_navigateto visit a public product page. - Extraction: Execute JavaScript to retrieve the nonce from the global parameters object defined in
assets/js/stripe.js.- JS Variable:
wc_stripe_params - Action Key: Based on the script localization logic, look for the nonce associated with the payment flow.
- Command:
browser_eval("wc_stripe_params.nonce.pay_for_order")(Note: The exact key insidewc_stripe_paramsmay be_ajax_nonceor nested under anoncekey; verify by inspecting thewc_stripe_paramsobject).
- JS Variable:
5. Exploitation Strategy
The goal is to force a pending order into a "failed" status.
- Target Identification: Identify a target Order ID (e.g., via sequential enumeration starting from a known recent order).
- Request Construction:
- Method: POST
- URL:
https://[TARGET]/index.php?wc-ajax=wc_stripe_pay_for_order - Headers:
Content-Type: application/x-www-form-urlencoded - Body Parameters:
order:[TARGET_ORDER_ID]payment_method:pi_12345_fake(Any string that causes a Stripe API failure)._wpnonce:[EXTRACTED_NONCE]
- Execution: Send the request using
http_request. - Observation: The server will likely return a JSON response containing an error message related to the failed payment, which confirms the
catchblock was reached.
6. Test Data Setup
- Configure Plugin:
- Enable WooCommerce and Stripe Gateway.
- Enable "Payment Request Buttons" in the Stripe settings (Checkout -> Stripe -> Settings).
- Create Content:
- Create a simple product.
- Create Target Order:
- As a guest user, add the product to the cart and proceed to checkout.
- Place an order using a "Manual" or "Cheque" method first, or simply leave the Stripe payment session unfinished to keep the order in
pendingstatus. - Record the Order ID (e.g.,
123).
7. Expected Results
- Response: The AJAX endpoint returns a JSON success or failure message (e.g.,
{"result":"failure","messages":"..."}). - State Change: The targeted order's status in the WooCommerce database changes from
pending(Pending Payment) tofailed. - Audit Trail: An order note is added stating that the payment failed, often including the fake payment ID or error message.
8. Verification Steps
- Check Status via CLI:
wp wc order get [ORDER_ID] --field=status --user=[ADMIN_USER]- If the result is
failed, the exploit was successful.
- If the result is
- Check Order Notes:
wp wc order_note list [ORDER_ID] --user=[ADMIN_USER]- Look for notes indicating a payment failure triggered via AJAX.
9. Alternative Approaches
- Order Enumeration: If the specific Order ID is unknown, the agent should attempt the request across a range of IDs (e.g.,
[LATEST_ID] - 10to[LATEST_ID]). - Different Nonce Sources: If the product page does not yield the nonce, check the Cart or Checkout pages using
browser_navigate. - Varying Payment Methods: If
pi_fakeis rejected by client-side validation (though this is a server-side check), try other invalid Stripe tokens liketok_chargeDeclined.
Summary
The WooCommerce Stripe Payment Gateway plugin suffers from a missing authorization vulnerability in its `ajax_pay_for_order()` function. An unauthenticated attacker can manipulate the status of pending orders to 'failed' by providing an arbitrary order ID and a malformed payment method to the `wc_stripe_pay_for_order` AJAX endpoint.
Vulnerable Code
// includes/class-wc-stripe-ajax.php (approximate line 130) public function ajax_pay_for_order() { check_ajax_referer( 'wc_stripe_pay_for_order', '_wpnonce' ); try { $order_id = isset( $_POST['order'] ) ? absint( $_POST['order'] ) : 0; $order = wc_get_order( $order_id ); if ( ! $order ) { throw new Exception( __( 'Invalid order.', 'woocommerce-gateway-stripe' ) ); } // ... processing payment method from $_POST['payment_method'] ... } catch ( Exception $e ) { // Vulnerability: The status is updated based on an invalid order ID / payment method without verifying ownership $order->update_status( 'failed', sprintf( __( 'Stripe payment failed: %s', 'woocommerce-gateway-stripe' ), $e->getMessage() ) ); wp_send_json_error( array( 'message' => $e->getMessage() ) ); } }
Security Fix
@@ -134,7 +134,9 @@ try { $order_id = isset( $_POST['order'] ) ? absint( $_POST['order'] ) : 0; $order = wc_get_order( $order_id ); + $order_key = isset( $_POST['order_key'] ) ? wc_clean( wp_unslash( $_POST['order_key'] ) ) : ''; - if ( ! $order ) { + if ( ! $order || ! $order->key_is_valid( $order_key ) ) { throw new Exception( __( 'Invalid order.', 'woocommerce-gateway-stripe' ) ); }
Exploit Outline
To exploit this vulnerability, an attacker first obtains a valid AJAX nonce for the `wc_stripe_pay_for_order` action, which is exposed in the `wc_stripe_params` JavaScript object on any product or cart page where Stripe Express Checkout is enabled. The attacker then sends a POST request to the `/?wc-ajax=wc_stripe_pay_for_order` endpoint. The payload must include the target `order` ID, the extracted `_wpnonce`, and an invalid or malformed `payment_method` string (e.g., 'pi_fake'). Because the server-side handler fails to verify if the requester owns the order or has a valid order key, it proceeds to process the payment. The invalid payment method triggers a Stripe API exception, and the plugin's error handler automatically updates the targeted order's status to 'failed', allowing for unauthenticated status manipulation via sequential order ID enumeration.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.