CVE-2026-4987

SureForms <= 2.5.2 - Unauthenticated Payment Amount Validation Bypass via 'form_id'

highImproper Input Validation
7.5
CVSS Score
7.5
CVSS Score
high
Severity
2.6.0
Patched in
1d
Time to patch

Description

The SureForms – Contact Form, Payment Form & Other Custom Form Builder plugin for WordPress is vulnerable to Payment Amount Bypass in all versions up to, and including, 2.5.2. This is due to the create_payment_intent() function performing a payment validation solely based on the value of a user-controlled parameter. This makes it possible for unauthenticated attackers to bypass configured form payment-amount validation and create underpriced payment/subscription intents by setting form_id to 0.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.5.2
PublishedMarch 27, 2026
Last updatedMarch 28, 2026
Affected pluginsureforms

What Changed in the Fix

Changes introduced in v2.6.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Vulnerability Research Plan: CVE-2026-4987 - SureForms Payment Bypass ## 1. Vulnerability Summary The **SureForms** plugin (up to version 2.5.2) contains a logic flaw in its payment processing workflow. The function `create_payment_intent()` (likely located in `SRFM\Inc\Payments\Stripe\Stripe_Hel…

Show full research plan

Vulnerability Research Plan: CVE-2026-4987 - SureForms Payment Bypass

1. Vulnerability Summary

The SureForms plugin (up to version 2.5.2) contains a logic flaw in its payment processing workflow. The function create_payment_intent() (likely located in SRFM\Inc\Payments\Stripe\Stripe_Helper or a similar payment-handling class) is responsible for generating a Stripe/payment provider intent.

The vulnerability arises because the server-side validation of the payment amount is conditionally linked to a provided form_id. If an attacker provides a form_id of 0, the application fails to look up the "official" price for the form and instead trusts the user-provided amount parameter. This allows an unauthenticated user to create a payment intent for an arbitrary (lower) value than intended by the site administrator.

2. Attack Vector Analysis

  • Endpoint: WordPress AJAX API (/wp-admin/admin-ajax.php).
  • Action: srfm_create_payment_intent (inferred from plugin prefix srfm and function name create_payment_intent).
  • HTTP Method: POST.
  • Authentication: Unauthenticated (requires wp_ajax_nopriv_ registration).
  • Vulnerable Parameter: form_id (set to 0) and amount.
  • Preconditions: The site must have Stripe or another payment provider configured within SureForms.

3. Code Flow

  1. Entry Point: A user interacts with a SureForms payment form on the frontend.
  2. AJAX Call: The frontend JS triggers a POST request to admin-ajax.php with the action srfm_create_payment_intent.
  3. Handler: The handler calls create_payment_intent().
  4. Bypass Logic (Vulnerable Path):
    • The code checks $_POST['form_id'].
    • If form_id is a valid post ID, it retrieves the form configuration: $form_settings = get_post_meta( $form_id, '_srfm_form_settings', true );.
    • It would then validate $_POST['amount'] against $form_settings['payment_amount'].
    • The Bug: If form_id is 0, the logic either skips the meta lookup or handles the empty result by defaulting to the value passed in the amount parameter without cross-referencing a source of truth.
  5. Sink: The unvalidated amount is passed to Stripe_Helper to create a PaymentIntent via the Stripe API.

4. Nonce Acquisition Strategy

SureForms likely localizes a nonce for frontend form submissions. Based on standard WordPress plugin patterns and the provided file list, the nonce is likely registered via wp_localize_script.

  • Strategy:
    1. Identify a page containing a SureForms payment form.
    2. The plugin uses the prefix srfm. The localized object is likely named srfm_vars or srfm_frontend.
    3. Action:
      • Create a test payment form using the plugin.
      • Place the form on a public page: wp post create --post_type=page --post_status=publish --post_content='[sureforms id="XYZ"]' (where XYZ is the form ID).
      • Navigate to the page using browser_navigate.
      • Execute browser_eval("window.srfm_frontend?.nonce") or browser_eval("window.srfm_vars?.nonce") to extract the token.
  • Verification: Check the wp_verify_nonce or check_ajax_referer call in the source (if accessible) to confirm the action string (e.g., srfm_nonce or srfm_form_nonce).

5. Exploitation Strategy

Step-by-Step Plan:

  1. Target Identification: Find a published SureForms payment form and its legitimate form_id. Note the actual price (e.g., $100.00).
  2. Nonce Extraction: Access the page where the form is hosted and extract the AJAX nonce from the srfm_frontend JS object.
  3. Draft Malicious Request:
    • URL: http://localhost:8080/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=srfm_create_payment_intent
      &_ajax_nonce=[EXTRACTED_NONCE]
      &form_id=0
      &amount=100
      &currency=usd
      &payment_method_id=pm_card_visa
      
      (Note: amount=100 represents $1.00 in Stripe's smallest currency unit).
  4. Execution: Send the request via http_request.
  5. Observation: A successful exploit will return a 200 OK with a JSON body containing a Stripe client_secret or success message, despite the amount being incorrect for the site's forms.

6. Test Data Setup

  1. Configure Plugin: Activate sureforms.
  2. Mock Payment Setup: If a real Stripe key isn't available, check if the plugin has a "Test Mode" or "Offline Payment" mode.
  3. Create High-Value Form:
    • Create a new form (Post Type: srfm_forms).
    • Set the payment amount to 10000 ($100.00).
    • Get the $REAL_FORM_ID.
  4. Publish Page:
    • wp post create --post_type=page --post_title="Payment Page" --post_status=publish --post_content='[sureforms id="$REAL_FORM_ID"]'

7. Expected Results

  • Success: The server returns a JSON response indicating a payment intent was created for the value 100 (e.g., {"success":true,"data":{"client_secret":"pi_..."}}).
  • Control Test: Sending the same request with the $REAL_FORM_ID and amount=100 should return an error (e.g., Invalid amount).

8. Verification Steps

  1. Check Database: Query the wp_srfm_entries table (referenced in admin/analytics.php) to see if a record was created with the manipulated amount.
    • wp db query "SELECT * FROM wp_srfm_entries ORDER BY id DESC LIMIT 1;"
  2. Check Post Meta: If the plugin saves transient payment data, check the metadata for the most recent entry.

9. Alternative Approaches

  • REST API: If the AJAX endpoint fails, check for a registered REST route. admin/analytics.php mentions register_rest_route. Look for a route like sureforms/v1/payments.
  • Parameter Variation: If form_id=0 is blocked by a global check, try form_id=null, form_id[]= or a non-existent ID (e.g., 999999) to see if the fallback to the user-supplied amount still occurs.
  • Subscription Bypass: The description mentions "subscription intents." If the form supports recurring payments, try adding plan_id or billing_cycle parameters while keeping form_id=0.
Research Findings
Static analysis — not yet PoC-verified

Summary

The SureForms plugin for WordPress is vulnerable to a payment amount validation bypass in the `create_payment_intent()` function. Due to improper input validation, unauthenticated attackers can set the `form_id` parameter to 0, causing the server to skip price verification against form settings and trust a user-provided amount instead.

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/sureforms/2.5.2/admin/admin.php /home/deploy/wp-safety.org/data/plugin-versions/sureforms/2.6.0/admin/admin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/sureforms/2.5.2/admin/admin.php	2026-03-12 11:46:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/sureforms/2.6.0/admin/admin.php	2026-03-23 10:59:44.000000000 +0000
@@ -108,8 +108,6 @@
 			add_action( 'admin_menu', [ $this, 'maybe_add_entries_badge' ], 99 );
 		}
 
-		add_action( 'admin_menu', [ $this, 'add_payments_badge' ], 99 );
-
 		add_filter( 'wpforms_current_user_can', [ $this, 'disable_wpforms_capabilities' ], 10, 3 );
 
 		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_pointer' ] );
@@ -662,34 +660,6 @@
 	}
 
 	/**
-	 * Summary of add_payments_badge
-	 *
-	 * @since 2.0.0
-	 * @return void
-	 */
-	public function add_payments_badge() {
-		if ( ! Helper::current_user_can() ) {
-			return;
-		}
-
-		global $submenu;
-		if ( isset( $submenu['sureforms_menu'] ) ) {
-			foreach ( $submenu['sureforms_menu'] as $index => $sub_item ) {
-				if ( isset( $sub_item[2] ) && SRFM_PAYMENTS === $sub_item[2] ) {
-					ob_start();
-					?>
-					<span style="color: #4ADE80;font-size: 9px;font-weight: 600;"><?php echo esc_html__( 'New', 'sureforms' ); ?></span>
-					<?php
-					$new_badge_html = ob_get_clean();
-					// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Adding notifications for submenu item.
-					$submenu['sureforms_menu'][ $index ][0] .= $new_badge_html;
-					break;
-				}
-			}
-		}
-	}
-
-	/**
 	 * Mark the user's visit to the entries page.
 	 *
 	 * @since 1.7.3

Exploit Outline

The exploit targets the AJAX endpoint used for creating Stripe payment intents. An unauthenticated attacker first obtains a valid AJAX nonce from the site's frontend (usually found in the `srfm_frontend` or `srfm_vars` JavaScript objects). The attacker then sends a POST request to `/wp-admin/admin-ajax.php` with the action `srfm_create_payment_intent`. By providing a `form_id` of `0` and a self-defined `amount` parameter, the attacker forces the plugin to ignore the server-side price configuration associated with real forms. If the plugin has an active payment provider like Stripe configured, it will return a valid payment intent (e.g., a `client_secret`) for the attacker's lower amount, allowing them to complete a transaction for an arbitrary price.

Check if your site is affected.

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