CVE-2026-2002

Forminator Forms – Contact Form, Payment Form & Custom Form Builder <= 1.50.2 - Authenticated (Administrator+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
4.4
CVSS Score
4.4
CVSS Score
medium
Severity
1.50.3
Patched in
1d
Time to patch

Description

The Forminator Forms – Contact Form, Payment Form & Custom Form Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the form_name parameter in all versions up to, and including, 1.50.2 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level access, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. The plugin allows admins to give form management permissions to lower level users, which could make this exploitable by users such as subscribers.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.50.2
PublishedFebruary 16, 2026
Last updatedFebruary 17, 2026
Affected pluginforminator

Source Code

WordPress.org SVN
Research Plan
Unverified

# Vulnerability Research Plan: CVE-2026-2002 (Forminator Forms Stored XSS) ## 1. Vulnerability Summary The **Forminator Forms** plugin (versions <= 1.50.2) contains a stored cross-site scripting (XSS) vulnerability. The vulnerability occurs because the plugin fails to sanitize or escape the `form_n…

Show full research plan

Vulnerability Research Plan: CVE-2026-2002 (Forminator Forms Stored XSS)

1. Vulnerability Summary

The Forminator Forms plugin (versions <= 1.50.2) contains a stored cross-site scripting (XSS) vulnerability. The vulnerability occurs because the plugin fails to sanitize or escape the form_name parameter when saving and subsequently displaying form titles. While primarily exploitable by Administrators, the plugin's "Permissions" feature allows Administrators to delegate form management to lower-privileged roles (e.g., Subscribers), significantly increasing the attack surface.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • AJAX Action: forminator_save_builder_custom_form (inferred based on Forminator's standard builder architecture).
  • Vulnerable Parameter: form_name
  • Authentication Required: Authenticated user with Forminator management permissions (Default: Administrator; can be delegated to Subscriber+).
  • Preconditions: The attacker must have access to the Forminator Form Builder interface.

3. Code Flow (Inferred)

  1. Entry: A POST request is sent to admin-ajax.php with the action forminator_save_builder_custom_form.
  2. Processing: The request is handled by the Forminator_Admin_AJAX or Forminator_Custom_Form_Admin class.
  3. Handling: The save_builder function extracts the form_name from the $_POST or $_REQUEST array.
  4. Storage: The form_name is stored in the wp_posts table (as post_title) or in wp_postmeta (as part of the form configuration) without being passed through sanitize_text_field.
  5. Sink: When an Administrator or an authorized user views the Forminator "Forms" dashboard or the specific form editor, the plugin retrieves the name and echoes it directly into the HTML without using esc_html or esc_attr.

4. Nonce Acquisition Strategy

Forminator uses a centralized AJAX object for its builder.

  1. Preparation: Create a test form to ensure the builder scripts are active.
    • wp post create --post_type=forminator_forms --post_title="Test Form" --post_status=publish
  2. Navigation: Log in as the user with permissions and navigate to the Forminator "Forms" dashboard: /wp-admin/admin.php?page=forminator-cform.
  3. Extraction: Use browser_eval to extract the nonce and the form_id.
    • Variable Name: window.Forminator.Data or window.forminator_admin_data (inferred).
    • Key: nonce or _wpnonce.
    • Command: browser_eval("window.forminator_admin_data.nonce")

5. Exploitation Strategy

Step 1: Obtain Nonce

Identify the form ID of an existing form and extract the required nonce from the dashboard.

Step 2: Inject Payload

Submit a craft AJAX request to update the form name with the XSS payload.

  • URL: https://[target]/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Parameters:
    • action: forminator_save_builder_custom_form
    • id: [FORM_ID]
    • form_name: <script>alert(document.domain)</script>
    • nonce: [EXTRACTED_NONCE]
    • data: {"settings":{"formName":"<script>alert(document.domain)</script>"}} (Forminator often expects JSON in the data parameter).

Step 3: Trigger Execution

Navigate to the Forminator dashboard at /wp-admin/admin.php?page=forminator-cform. The script will execute in the context of the viewing user (likely an Administrator).

6. Test Data Setup

  1. Target User: Create a Subscriber user.
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
  2. Plugin Configuration: As Administrator, go to Forminator > Settings > Permissions and enable "Custom Forms" management for the "Subscriber" role.
  3. Target Form: Create a dummy form to be edited.
    • wp post create --post_type=forminator_forms --post_title="Original Name" --post_status=publish

7. Expected Results

  • The AJAX request should return a success response (typically {"success":true,...}).
  • The wp_posts table should show the post_title for the form updated with the <script> tag.
  • When the dashboard is loaded, a JavaScript alert box displaying the domain name should appear.

8. Verification Steps

  1. Check Database via CLI:
    • wp db query "SELECT post_title FROM wp_posts WHERE post_type='forminator_forms' LIMIT 1"
    • Verify the title contains the <script> tag.
  2. Verify Rendering:
    • Use http_request to GET /wp-admin/admin.php?page=forminator-cform.
    • Search the response body for the raw, unescaped payload: <script>alert(document.domain)</script>.

9. Alternative Approaches

  • Payload Location: If form_name in the top-level parameter is sanitized, attempt injection via the data parameter's JSON structure: data[settings][formName].
  • Different Form Types: If "Custom Forms" are patched but other types aren't, try the same payload with:
    • action: forminator_save_quiz_backend
    • action: forminator_save_poll_backend
  • Form Export/Import: Check if the XSS can be triggered via the "Import" functionality by uploading a JSON file with a malicious form_name.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Forminator Forms plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'form_name' parameter. This occurs due to a lack of sanitization during form creation/editing and insufficient output escaping, allowing authorized users (including lower-privileged roles if delegated) to execute arbitrary scripts in the context of other users viewing the dashboard.

Vulnerable Code

// forminator/library/modules/custom-forms/admin/admin-ajax.php

public function save_builder() {
    // ... 
    $id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : 0;
    $form_name = isset( $_POST['form_name'] ) ? $_POST['form_name'] : ''; // No sanitization
    
    $form_model = Forminator_Base_Form_Model::get_model( $id );
    if ( is_object( $form_model ) ) {
        $form_model->name = $form_name;
        $form_model->save();
    }
    // ...
}

---

// forminator/admin/views/custom-forms/list.php

<td class="column-title">
    <strong>
        <a href="<?php echo $edit_url; ?>"><?php echo $form['name']; ?></a> <!-- No escaping -->
    </strong>
</td>

Security Fix

--- a/forminator/library/modules/custom-forms/admin/admin-ajax.php
+++ b/forminator/library/modules/custom-forms/admin/admin-ajax.php
@@ -20,7 +20,7 @@
 	public function save_builder() {
 		// ...
 		$id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : 0;
-		$form_name = isset( $_POST['form_name'] ) ? $_POST['form_name'] : '';
+		$form_name = isset( $_POST['form_name'] ) ? sanitize_text_field( $_POST['form_name'] ) : '';
 		// ...
--- a/forminator/admin/views/custom-forms/list.php
+++ b/forminator/admin/views/custom-forms/list.php
@@ -45,7 +45,7 @@
 <td class="column-title">
 	<strong>
-		<a href="<?php echo $edit_url; ?>"><?php echo $form['name']; ?></a>
+		<a href="<?php echo esc_url( $edit_url ); ?>"><?php echo esc_html( $form['name'] ); ?></a>
 	</strong>
 </td>

Exploit Outline

1. Authentication: Log in as a user with form management permissions (typically Administrator, but can be delegated to Subscribers via Forminator > Settings > Permissions). 2. Nonce Retrieval: Access the Forminator dashboard and extract the AJAX nonce from the source code (typically found in the 'forminator_admin_data' JavaScript object). 3. Injection: Send a POST request to '/wp-admin/admin-ajax.php' using the action 'forminator_save_builder_custom_form' (or 'forminator_save_quiz_backend'/'forminator_save_poll_backend'). 4. Payload: Set the 'form_name' parameter to a malicious payload, such as '<script>alert(document.domain)</script>'. 5. Execution: Wait for an administrator to view the 'Forms', 'Quizzes', or 'Polls' list page. The script will execute in their session.

Check if your site is affected.

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