Fluent Forms <= 6.1.7 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Form Creation via AI Builder
Description
The Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder plugin for WordPress is vulnerable to Missing Authorization in all versions up to, and including, 6.1.7. This is due to missing capability checks on the `fluentform_ai_create_form` AJAX action. This makes it possible for authenticated attackers, with Subscriber-level access and above, to create arbitrary forms via the publicly exposed AI builder.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=6.1.7Source Code
WordPress.org SVNThis research plan outlines the steps required to demonstrate the missing authorization vulnerability in Fluent Forms (CVE-2025-13722). ### 1. Vulnerability Summary The vulnerability exists in the `fluentform_ai_create_form` AJAX action of the Fluent Forms plugin. While the functionality is intende…
Show full research plan
This research plan outlines the steps required to demonstrate the missing authorization vulnerability in Fluent Forms (CVE-2025-13722).
1. Vulnerability Summary
The vulnerability exists in the fluentform_ai_create_form AJAX action of the Fluent Forms plugin. While the functionality is intended for administrators using the AI form builder, the AJAX handler lacks a capability check (e.g., current_user_can('manage_options')). This oversight allows any authenticated user, including those with the lowest privileges (Subscriber), to invoke the function and create arbitrary forms on the WordPress site.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
fluentform_ai_create_form - Method: POST
- Required Authentication: Subscriber level or higher.
- Preconditions: The AI Builder module must be active (usually active by default if the plugin is configured).
- Vulnerable Parameter: The payload likely expects form configuration data, possibly under a
form_dataordatakey, or parameters describing the form "prompt" if the AI processing happens server-side.
3. Code Flow (Inferred from Patch and Description)
- Registration: The plugin registers the action:
add_action('wp_ajax_fluentform_ai_create_form', [$this, 'createFormViaAi']); - Trigger: A Subscriber user sends a POST request to
admin-ajax.phpwithaction=fluentform_ai_create_form. - Vulnerable Function: The handler function (e.g.,
createFormViaAi) is called. - Authorization Gap: The function likely checks for a valid nonce but fails to verify if the user has the
fluentform_dashboard_accessormanage_optionscapability. - Sink: The function processes the input and calls
FluentForm\App\Modules\Form\Form::create(), inserting a new form into thewp_fluentform_formstable.
4. Nonce Acquisition Strategy
Fluent Forms typically localizes its AJAX nonces for the admin dashboard. Even a Subscriber user can access the WordPress admin dashboard (/wp-admin/index.php), where the plugin may enqueue its global variables.
- Identify Variable: Fluent Forms often uses
fluent_forms_global_varorfluentform_admin_vars. - Creation/Navigation:
- Log in as a Subscriber.
- Navigate to
/wp-admin/.
- Extraction:
- Use
browser_evalto find the nonce:browser_eval("window.fluent_forms_global_var?.nonce")orbrowser_eval("window.fluentform_admin_vars?.nonce"). - If the nonce is not present on the dashboard, it may be necessary to navigate to a page where a Fluent Form is rendered.
- Use
5. Exploitation Strategy
The goal is to create a form with a specific title to prove unauthorized creation.
Step 1: Setup a Subscriber User
Create a low-privileged user to act as the attacker.
Step 2: Authenticate and Extract Nonce
Use the browser_navigate tool to log in as the Subscriber and find the required nonce.
Step 3: Send the Exploitation Request
Submit the malicious request to create a form.
- URL:
http://[target]/wp-admin/admin-ajax.php - Content-Type:
application/x-www-form-urlencoded - Body Parameters:
action:fluentform_ai_create_formnonce:[EXTRACTED_NONCE]title:Exploit Form(inferred parameter)form_settings:{"title": "Pwned Form"}(inferred JSON structure)
Note: Since the exact structure of the AI generation payload may vary, if the initial attempt fails, the agent should inspect the plugin's JS assets (e.g., assets/js/fluent_form_admin.js) to find the exact key-value pairs sent to fluentform_ai_create_form.
6. Test Data Setup
- Install Plugin: Ensure Fluent Forms version <= 6.1.7 is installed and active.
- Create User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
7. Expected Results
- Response: The server should return a JSON response, likely containing the ID of the newly created form:
{"success": true, "data": {"form_id": X}}. - HTTP Status: 200 OK.
- Unauthorized State: A Subscriber-level user has successfully performed an administrative action.
8. Verification Steps
After sending the HTTP request, verify the form creation using WP-CLI:
# List all forms and look for "Exploit Form"
wp db query "SELECT title FROM wp_fluentform_forms WHERE title = 'Exploit Form';"
Alternatively, if the fluentform CLI is available:
wp fluentform forms list
9. Alternative Approaches
- JSON Payload: If
x-www-form-urlencodedfails, the AI builder might expect a raw JSON POST body. Try sendingContent-Type: application/jsonwith the action included in the JSON. - Context Discovery: If the nonce is not found in the global object, use
browser_evalto search for all localized scripts:browser_eval("Object.keys(window).filter(key => key.toLowerCase().includes('fluent'))") - AI Prompt Payload: The "AI Builder" might require a "prompt" string rather than a direct form structure. Try:
action=fluentform_ai_create_form&prompt=Create+a+simple+contact+form&nonce=[NONCE]
Summary
The Fluent Forms plugin for WordPress is vulnerable to a missing authorization check on the 'fluentform_ai_create_form' AJAX action. This allows authenticated users with minimal privileges, such as Subscribers, to bypass intended administrative restrictions and create arbitrary forms via the AI builder functionality.
Vulnerable Code
// fluentform/app/Modules/AIBuider/Handler.php (approximate) public function createFormViaAi() { $nonce = $_REQUEST['nonce']; if (!wp_verify_nonce($nonce, 'fluentform_admin_ajax_nonce')) { wp_send_json_error(['message' => 'Invalid nonce'], 422); } // Vulnerability: Lack of capability check (e.g., current_user_can('fluentform_dashboard_access')) // permits any authenticated user to trigger the form creation logic below. $prompt = sanitize_text_field($_POST['prompt']); // ... processing logic to generate and save the form ... }
Security Fix
@@ -10,6 +10,10 @@ wp_send_json_error(['message' => 'Invalid nonce'], 422); } + if (!current_user_can('fluentform_dashboard_access')) { + wp_send_json_error(['message' => 'Permission denied'], 403); + } + $prompt = sanitize_text_field($_POST['prompt']);
Exploit Outline
The exploit involves an authenticated attacker with Subscriber-level access targeting the admin AJAX endpoint. The attacker first extracts a valid AJAX nonce from the 'fluentform_admin_vars' or 'fluent_forms_global_var' JavaScript objects available in the WordPress dashboard. Then, they send a POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'fluentform_ai_create_form', including the nonce and a 'prompt' or 'title' parameter describing the form they wish to create. Because the server-side handler fails to check for administrative capabilities, it processes the request and creates a new form entry in the database.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.