CVE-2026-42752

Accept Stripe Payments <= 2.0.98 - Unauthenticated Payment Bypass

mediumAuthorization Bypass Through User-Controlled Key
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.0.99
Patched in
4d
Time to patch

Description

The Accept Stripe Payments plugin for WordPress is vulnerable to Payment Bypasses in all versions up to, and including, 2.0.98. This makes it possible for unauthenticated attackers to bypass payments.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.0.98
PublishedMay 29, 2026
Last updatedJune 2, 2026
Affected pluginstripe-payments

What Changed in the Fix

Changes introduced in v2.0.99

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-42752 - Accept Stripe Payments Payment Bypass ## 1. Vulnerability Summary The **Accept Stripe Payments** plugin (<= 2.0.98) is vulnerable to an unauthenticated payment bypass. The core issue resides in the handling of payment results in `ASP_Process_IPN_NG::handle_next_act…

Show full research plan

Research Plan: CVE-2026-42752 - Accept Stripe Payments Payment Bypass

1. Vulnerability Summary

The Accept Stripe Payments plugin (<= 2.0.98) is vulnerable to an unauthenticated payment bypass. The core issue resides in the handling of payment results in ASP_Process_IPN_NG::handle_next_action_results. The plugin allows a user-controlled parameter, is_live, to determine whether the transaction should be validated against Stripe's Live or Test API keys.

An attacker can complete a transaction using a test card via the plugin's test configuration and then force the server to validate that "test" payment as a successful purchase by setting is_live=false in the results handler, even for products intended for live production.

2. Attack Vector Analysis

  • Endpoints:
    1. wp-admin/admin-ajax.php?action=asp_pp_save_form_data (Session Setup)
    2. wp-admin/admin-ajax.php?action=asp_next_action_results (Bypass Trigger)
  • Vulnerable Parameter: is_live (GET) and payment_intent (GET).
  • Authentication: None required (nopriv AJAX actions).
  • Preconditions:
    • A product must be created.
    • Stripe Test API keys must be configured in the plugin settings (often left in place after development or used for "Sandbox" mode).

3. Code Flow

  1. Entry Point 1 (Session Setup): The attacker calls asp_pp_save_form_data. The plugin saves product details (like product_id) into a server-side session transient (asp_pp_form_data).
  2. Entry Point 2 (The Bypass): The attacker calls asp_next_action_results.
  3. Key Selection (includes/class-asp-process-ipn-ng.php):
    $is_live = isset( $_GET['is_live'] ) ? sanitize_text_field( stripslashes ( $_GET['is_live'] ) ) : '';
    $is_live = 'false' === $is_live ? 0 : 1;
    // ...
    $key = $is_live ? $this->asp_class->APISecKey : $this->asp_class->APISecKeyTest;
    \Stripe\Stripe::setApiKey( $key );
    
  4. Verification: The plugin retrieves the payment_intent using the selected $key. If the attacker provides a payment_intent created in the Stripe test environment and sets is_live=false, the retrieval succeeds.
  5. Fulfillment: The plugin calls process_ipn(), which validates the amount and fulfills the order (e.g., provides download links or marks the order as paid).

4. Nonce Acquisition Strategy

The action asp_pp_save_form_data is used to initialize the session. While its implementation is not fully shown, it is likely authenticated by asp_pp_ajax_nonce.

  1. Identify Shortcode: The plugin uses [asp_product id="ID"] to display products.
  2. Create Page: Create a test page containing the product shortcode.
  3. Extract Nonce:
    • Navigate to the product page.
    • The nonce and other metadata are localized via wp_localize_script.
    • In the source of public/views/templates/default/payment-popup.php, variables are printed into the global scope.
    • Use browser_eval to find the nonce: browser_eval("window.stripe_payments_vars?.nonce") or check for asp_pp_ajax_nonce in the script tags.

5. Exploitation Strategy

Step 1: Session Setup

Call asp_pp_save_form_data to associate the current session with the target product.

  • URL: http://vulnerable-test.local/wp-admin/admin-ajax.php
  • Method: POST
  • Payload (application/x-www-form-urlencoded):
    • action: asp_pp_save_form_data
    • nonce: [EXTRACTED_NONCE]
    • asp_product_id: [TARGET_PRODUCT_ID]

Step 2: Bypass Trigger

Invoke the results handler with is_live=false and a test payment_intent.

  • URL: http://vulnerable-test.local/wp-admin/admin-ajax.php?action=asp_next_action_results&is_live=false&payment_intent=[TEST_PI_ID]
  • Method: GET
  • Note: In a real-world scenario, [TEST_PI_ID] is obtained by completing a checkout using Stripe's test card (e.g., 4242...) against the site's public test key. For a PoC in an isolated environment, if Stripe API connectivity is unavailable, the goal is to demonstrate that the code switches to APISecKeyTest based on the GET parameter.

6. Test Data Setup

  1. Configure Plugin:
    # Set dummy Stripe keys
    wp option update AcceptStripePayments-settings '{"api_key_test_public":"pk_test_dummy","api_key_test_secret":"sk_test_dummy","api_key_live_public":"pk_live_dummy","api_key_live_secret":"sk_live_dummy"}' --format=json
    
  2. Create Product:
    wp post create --post_type=asp-products --post_title="Expensive Digital Asset" --post_status=publish
    # Capture the generated Product ID
    
  3. Place Shortcode:
    wp post create --post_type=page --post_title="Store" --post_content='[asp_product id="[ID]"]' --post_status=publish
    

7. Expected Results

  • The asp_next_action_results call should trigger process_ipn.
  • The plugin should redirect to the "Thank You" page (checkout_url setting).
  • The plugin's debug log (if enabled via ASP_Debug_Logger) should show "Payment processing started" using the test key despite the product potentially being live.

8. Verification Steps

After the HTTP requests, verify the order creation via WP-CLI:

# Check for new orders (post_type: stripe_order)
wp post list --post_type=stripe_order --orderby=post_date --order=DESC

The order status should be marked as complete/paid even though no real funds were transferred.

9. Alternative Approaches

If asp_pp_save_form_data is strictly nonced or session-locked, examine the wp_loaded entry point in includes/class-asp-process-ipn-ng.php:

$process_ipn = filter_input( INPUT_POST, 'asp_process_ipn', FILTER_SANITIZE_NUMBER_INT );
if ( $process_ipn ) {
    add_action( 'wp_loaded', array( $this, 'process_ipn' ), 2147483647 );
}

This path directly hits process_ipn() without the AJAX wrapper, potentially allowing an attacker to bypass the is_live check entirely if they can inject POST data that process_ipn trusts. Check if process_ipn verifies the payment_intent independently of the is_live logic used in the AJAX handler.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Accept Stripe Payments plugin for WordPress is vulnerable to an unauthenticated payment bypass due to the use of a user-controlled parameter to select the Stripe API environment (Live vs. Test). An attacker can submit a Stripe test-mode payment intent and force the plugin to validate it against test credentials by setting 'is_live=false', resulting in order fulfillment for live products without actual payment.

Vulnerable Code

// includes/class-asp-process-ipn-ng.php line 64
		$pi_id = isset( $_GET['payment_intent'] ) ? sanitize_text_field( stripslashes ( $_GET['payment_intent'] ) ) : '';

		$is_live = isset( $_GET['is_live'] ) ? sanitize_text_field( stripslashes ( $_GET['is_live'] ) ) : '';
		$is_live = 'false' === $is_live ? 0 : 1;

---

// includes/class-asp-process-ipn-ng.php line 81
		try {

			ASPMain::load_stripe_lib();
			$key = $is_live ? $this->asp_class->APISecKey : $this->asp_class->APISecKeyTest;
			\Stripe\Stripe::setApiKey( $key );

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/accept-stripe-payments.php /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/accept-stripe-payments.php
--- /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/accept-stripe-payments.php	2026-04-19 11:04:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/accept-stripe-payments.php	2026-05-07 03:52:06.000000000 +0000
@@ -3,7 +3,7 @@
 /**
  * Plugin Name: Accept Stripe Payments
  * Description: Easily accept credit card payments via Stripe payment gateway in WordPress.
- * Version: 2.0.98
+ * Version: 2.0.99
  * Author: Tips and Tricks HQ, wptipsntricks
  * Author URI: https://www.tipsandtricks-hq.com/
  * Plugin URI: https://s-plugins.com
@@ -19,7 +19,7 @@
 	exit; 
 }
 
-define( 'WP_ASP_PLUGIN_VERSION', '2.0.98' );
+define( 'WP_ASP_PLUGIN_VERSION', '2.0.99' );
 define( 'WP_ASP_MIN_PHP_VERSION', '7.4' );
 define( 'WP_ASP_PLUGIN_URL', plugins_url( '', __FILE__ ) );
 define( 'WP_ASP_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-payment-data.php /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-payment-data.php
--- /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-payment-data.php	2025-08-16 09:48:08.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-payment-data.php	2026-05-07 03:52:06.000000000 +0000
@@ -264,6 +264,11 @@
 
 		$this->billing_details_obj = $bd;
 
+		$cd = new stdClass;
+		$cd->name = $b_name;
+		$cd->email = $b_email;
+		$this->customer_obj = $cd;
+
 		//Shipping details
 		$same_addr = $ipn_ng_class->get_post_var( 'asp_same-bill-ship-addr' );
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-pp-ajax.php /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-pp-ajax.php
--- /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-pp-ajax.php	2025-08-16 09:48:08.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-pp-ajax.php	2026-05-07 03:52:06.000000000 +0000
@@ -700,6 +700,9 @@
 		$zero_value_id           = str_replace( '.', '', uniqid( 'free_', true ) );
 		$coupon['zero_value_id'] = $zero_value_id;
 
+        // Saving applied coupon signature data so that it can be verified later.
+		ASP_Utils_Bot_Mitigation::record_full_discount_signature_data($product_id, $coupon_code, $zero_value_id);
+
 		wp_send_json( $coupon );
 
 	}
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-process-ipn-ng.php /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-process-ipn-ng.php
--- /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-process-ipn-ng.php	2026-04-19 11:04:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-process-ipn-ng.php	2026-05-07 03:52:06.000000000 +0000
@@ -215,6 +215,12 @@
 			//ASP_Debug_Logger::log( 'Original $_POST data: ' . $post_data_str );
 		}
 
+		$nonce = $this->get_post_var( 'asp_payment_form_nonce' );
+		if (empty($nonce) || !wp_verify_nonce($nonce, 'asp_payment_form_nonce')){
+			// ASP_Debug_Logger::log( 'Nonce verification failed!', false);
+			wp_die( __('Nonce verification failed!', 'stripe-payments') );
+		}
+
 		do_action( 'asp_ng_before_payment_processing', $post_data );
 
 		$this->sess = ASP_Session::get_instance();
@@ -812,6 +818,14 @@
 			//this is zero-value transaction
 			$coupon_code = $this->get_post_var( 'asp_coupon-code' );
 			$coupon_code = sanitize_text_field( stripslashes( $coupon_code ));
+			$prod_id = $this->item->get_product_id();
+
+			// Check coupon signature data
+			if( empty(ASP_Utils_Bot_Mitigation::is_full_discount_signature_data_valid($prod_id, $coupon_code, $pi)) ){
+				// Signature is invalid.
+				wp_die(sprintf(__( "Error! Invalid transaction ID: %s", 'stripe-payments' ), $pi));
+			}
+
 			if ( empty( $coupon_code ) ) {
 				return $p_data;
 			}
@@ -829,8 +843,6 @@
 				return $p_data;
 			}
 
-			$prod_id = $this->item->get_product_id();
-
 			$order = new ASP_Order_Item();
 
 			if ( $order->can_create( $prod_id ) ) {
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-product-item.php /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-product-item.php
--- /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-product-item.php	2025-12-01 09:38:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-product-item.php	2026-05-07 03:52:06.000000000 +0000
@@ -535,6 +535,18 @@
 		if ( ! $this->coupon ) {
 			return false;
 		}
+
+		// First check if coupon option is enabled for this product. (the settings in edit product page)
+		$coupons_enabled_for_product = get_post_meta( $this->post_id, 'asp_product_coupons_setting', true );
+		if ( $coupons_enabled_for_product === '' || $coupons_enabled_for_product == '2') {
+			$coupons_enabled_for_product = $this->asp_main->get_setting( 'coupons_enabled' );
+		}
+		if ( empty( $coupons_enabled_for_product ) ){
+			$this->last_error = __( 'Applying coupon is not allowed for this product.', 'stripe-payments' );
+			$this->coupon = false;
+			return false;
+		}
+
 		//check if coupon is allowed for the product
 		$only_for_allowed_products = get_post_meta( $this->coupon['id'], 'asp_coupon_only_for_allowed_products', true );
 		if ( $only_for_allowed_products ) {
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-utils-bot-mitigation.php /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-utils-bot-mitigation.php
--- /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/includes/class-asp-utils-bot-mitigation.php	2025-08-16 09:48:08.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/includes/class-asp-utils-bot-mitigation.php	2026-05-07 03:52:06.000000000 +0000
@@ -140,7 +140,81 @@
         
         return true;
     }
-    
+
+	public static function get_full_discount_signature_data() {
+		$full_discount_signature_arr = get_transient('asp_full_discount_signature');
+		if (!isset($full_discount_signature_arr) || empty($full_discount_signature_arr)) {
+			$full_discount_signature_arr = array();
+		}
+		return $full_discount_signature_arr;
+	}
+
+	public static function record_full_discount_signature_data($product_id, $coupon_code, $txn_id) {
+		$ip_address = ASP_Utils::get_user_ip_address();
+		//$current_wp_time = current_time('mysql');
+
+		if (!isset($ip_address) || empty($ip_address)) {
+			ASP_Debug_Logger::log('IP address value missing (could not read the IP address of the user). Cannot record this full discount signature. The extra security check later may fail.', false);
+			return;
+		}
+
+		$full_discount_signature_arr = ASP_Utils_Bot_Mitigation::get_full_discount_signature_data();
+
+		$hp_key = ASP_Utils_Bot_Mitigation::get_asp_hash_private_key_one();
+
+		$index = $product_id. '_' .$ip_address. '_' .$txn_id;
+		$signature = sha1($hp_key.$product_id.$coupon_code.$ip_address.$txn_id);
+		$full_discount_signature_arr[$index] = $signature;
+
+		// TODO: Debug Purpose Only.
+		//ASP_Debug_Logger::log('Index: ' . $index . ', Signature: ' . $signature . ', IP Address: ' . $ip_address . ' Txn ID: '. $txn_id);
+		//ASP_Debug_Logger::log_array_data($full_discount_signature_arr);
+
+		//Save the page load signature data with an expiry of 1 hour.
+		set_transient('asp_full_discount_signature', $full_discount_signature_arr, 3600);
+	}
+
+	public static function is_full_discount_signature_data_valid($product_id, $coupon_code, $txn_id) {
+		$full_discount_signature_arr = get_transient('asp_full_discount_signature');
+		if (!isset($full_discount_signature_arr) || empty($full_discount_signature_arr)) {
+			ASP_Debug_Logger::log( 'Full discount signature check - currently there are no entries in the saved data for full discount signature.', false );
+			return false;
+		}
+
+		$ip_address_to_check = ASP_Utils::get_user_ip_address();
+		if (!isset($ip_address_to_check) || empty($ip_address_to_check)) {
+			ASP_Debug_Logger::log( 'Full discount signature check - IP address value for this request is missing.', false );
+			return false;
+		}
+
+		$index = $product_id. '_'.$ip_address_to_check. '_' .$txn_id;
+
+		if (!isset($full_discount_signature_arr[$index]) || empty ($full_discount_signature_arr[$index])){
+			ASP_Debug_Logger::log( 'Full discount check - cannot find this Product ID and IP address index ('.$index.') in the saved data.', false );
+			return false;
+		}
+
+		$hp_key = ASP_Utils_Bot_Mitigation::get_asp_hash_private_key_one();
+
+		$expected_signature = $full_discount_signature_arr[$index];
+		$received = sha1($hp_key.$product_id.$coupon_code.$ip_address_to_check.$txn_id);
+
+		// TODO: Debug Purpose Only.
+		//ASP_Debug_Logger::log('Index: ' . $index . ', Signature Received: ' . $received . ', Expected: ' . $expected_signature, true);
+		//ASP_Debug_Logger::log_array_data($full_discount_signature_arr);
+
+		if (!hash_equals($expected_signature, $received)){
+			//Mis-match
+			ASP_Debug_Logger::log( 'Full discount check - the signature hash does not match.', false );
+			return false;
+		}
+
+		//Entry for the received signature exists
+		ASP_Debug_Logger::log( 'Full discount check done!' );
+
+		return true;
+	}
+
     public static function get_request_limit_count_data() {
         $asp_request_usage_count = get_transient('asp_request_usage_count');
         if (!isset($asp_request_usage_count) || empty($asp_request_usage_count)) {
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/public/views/templates/default/payment-popup.php /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/public/views/templates/default/payment-popup.php
--- /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/public/views/templates/default/payment-popup.php	2025-08-16 09:48:08.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/public/views/templates/default/payment-popup.php	2026-05-07 03:52:06.000000000 +0000
@@ -513,6 +513,7 @@
 						<input type="hidden" id="btn-uniq-id" name="btn_uniq_id" value="<?php echo ! empty( $a['btn_uniq_id'] ) ? esc_attr( $a['btn_uniq_id'] ) : ''; ?>">
 						<input type="hidden" id="product-id" name="product_id" value="<?php echo esc_attr( $a['prod_id'] ); ?>">
 						<input type="hidden" name="process_ipn" value="1">
+                        <input type="hidden" value="<?php echo esc_attr(wp_create_nonce('asp_payment_form_nonce')) ?>" name="payment_form_nonce">
 						<input type="hidden" name="is_live" value="<?php echo $a['is_live'] ? '1' : '0'; ?>">
 						<?php if ( $a['data']['url'] ) { ?>
 						<input type="hidden" name="item_url" value="<?php echo esc_attr( $a['data']['url'] ); ?>">
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/readme.txt /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/readme.txt
--- /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.98/readme.txt	2026-04-19 11:04:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/stripe-payments/2.0.99/readme.txt	2026-05-07 03:52:06.000000000 +0000
@@ -5,7 +5,7 @@
 Requires at least: 6.0
 Tested up to: 7.0
 Requires PHP: 7.4
-Stable tag: 2.0.98
+Stable tag: 2.0.99
 License: GPLv2 or later
 License URI: http://www.gnu.org/licenses/gpl-2.0.html
 
@@ -157,6 +157,12 @@
 
 == Changelog ==
 
+= 2.0.99 =
+- Added a server-side check to verify that the coupon feature is enabled before attempting to apply a coupon code.
+- Added an additional verification check for 100% discount coupons.
+- Added nonce verification to the form submission process.
+- Fixed a minor PHP warning on the checkout result page during 100% discount checkout scenarios.
+
 = 2.0.98 =
 - Added prefixes to the Gutenberg block variables to prevent potential conflicts with other plugins.
 - Added new filter hook 'asp_process_ipn_order_status' to allow modification of the order status before changing the order status in the IPN processing code.

Exploit Outline

The exploit involves two main steps: session initialization and payment environment manipulation. First, an unauthenticated attacker calls the 'asp_pp_save_form_data' AJAX endpoint to associate their session with a specific target product ID. Second, the attacker generates a legitimate Stripe Payment Intent using a test-mode credit card (against the site's public test key). Finally, the attacker triggers the 'asp_next_action_results' endpoint while passing 'is_live=false' and the test payment intent ID. Because the plugin uses the 'is_live' parameter to determine which secret key to use for server-side verification, it validates the test-mode intent against the plugin's test credentials and proceeds to fulfill the order for the live product.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.