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.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- 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, "