CVE-2026-5436

MW WP Form <= 5.1.1 - Unauthenticated Arbitrary File Move via regenerate_upload_file_keys

highImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
8.1
CVSS Score
8.1
CVSS Score
high
Severity
5.1.2
Patched in
7d
Time to patch

Description

The MW WP Form plugin for WordPress is vulnerable to Arbitrary File Move/Read in all versions up to and including 5.1.1. This is due to insufficient validation of the $name parameter (upload field key) passed to the generate_user_file_dirpath() function, which uses WordPress's path_join() — a function that returns absolute paths unchanged, discarding the intended base directory. The attacker-controlled key is injected via the mwf_upload_files[] POST parameter, which is loaded into the plugin's Data model via _set_request_valiables(). During form processing, regenerate_upload_file_keys() iterates over these keys and calls generate_user_filepath() with the attacker-supplied key as the $name argument — 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 same 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. This makes it possible for unauthenticated attackers to move arbitrary files on the server, which can easily lead to remote code execution when the right file is moved (such as wp-config.php). The vulnerability is only exploitable if a file upload field is added to the form and the “Saving inquiry data in database” option is enabled.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.1.1
PublishedApril 8, 2026
Last updatedApril 15, 2026
Affected pluginmw-wp-form

What Changed in the Fix

Changes introduced in v5.1.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the exploitation of **CVE-2026-5436**, an unauthenticated arbitrary file move/read vulnerability in the **MW WP Form** plugin. ### 1. Vulnerability Summary The vulnerability exists due to improper path validation in `MW_WP_Form_Directory::generate_user_file_dirpath()`. T…

Show full research plan

This research plan outlines the exploitation of CVE-2026-5436, an unauthenticated arbitrary file move/read vulnerability in the MW WP Form plugin.

1. Vulnerability Summary

The vulnerability exists due to improper path validation in MW_WP_Form_Directory::generate_user_file_dirpath(). The function uses WordPress's path_join() to combine a base temporary directory with a user-supplied directory name ($name). If $name is an absolute path, path_join() returns it unchanged, allowing an attacker to escape the intended directory.

When a form is submitted, the plugin processes file attachments. If the "Saving inquiry data in database" option is enabled, it resolves the file paths and moves them from the temporary directory to the permanent uploads directory using rename(). By injecting an absolute path as the key in the mwf_upload_files[] parameter, an attacker can trick the plugin into moving arbitrary files (like wp-config.php) from their original location into the publicly accessible uploads folder.

2. Attack Vector Analysis

  • Endpoint: The main site frontend where an mwform shortcode is rendered.
  • Hook: template_redirect (via MW_WP_Form_Main_Controller::_template_redirect()).
  • Vulnerable Parameter: mwf_upload_files[] (an array where the key is the directory path and the value is the filename).
  • Authentication: Unauthenticated.
  • Preconditions:
    1. A form must exist with at least one file upload field.
    2. The "Saving inquiry data in database" option must be enabled for that form.
    3. The attacker must know the absolute path to the target file (standard WordPress installs use /var/www/html/).

3. Code Flow

  1. Request Entry: MW_WP_Form_Main_Controller::_template_redirect() (Line 121, class.main.php) is called.
  2. Data Ingestion: MW_WP_Form_Data::connect() (Line 144, class.main.php) is called, which triggers _set_request_valiables() (Line 183, class.data.php), loading all $_POST data (including the malicious mwf_upload_files array) into the $this->variables property.
  3. Condition Check: If $_POST['token'] is provided and no buttons are pressed, get_post_condition() returns 'complete'.
  4. File Processing: _template_redirect calls _file_upload() (Line 160, class.main.php), which calls MWF_Functions::regenerate_upload_file_keys().
  5. Path Resolution: regenerate_upload_file_keys calls MW_WP_Form_Directory::generate_user_filepath($form_id, $name, $filename).
  6. Traversal Sink: generate_user_file_dirpath($form_id, $name) (Line 64, class.directory.php) calls path_join($user_dir, $name). If $name is /var/www/html/, it returns /var/www/html/.
  7. Validation Bypass: generate_user_filepath (Line 150) checks is_dir($user_file_dir). Since /var/www/html/ exists, it proceeds. The final path becomes /var/www/html/wp-config.php.
  8. The Move: MW_WP_Form_Main_Controller::_send() (Line 245, class.main.php) identifies that database saving is enabled (usedb). It calls MWF_Functions::move_temp_file_to_upload_dir(), which executes rename('/var/www/html/wp-config.php', '.../wp-content/uploads/mw-wp-form_uploads/...').

4. Nonce Acquisition Strategy

The plugin uses a custom CSRF token implementation (MW_WP_Form_Csrf). The token is required for the _template_redirect logic to proceed.

  1. Identify Form: Find a page containing a form generated by the plugin.
  2. Navigate: Use browser_navigate to that page.
  3. Extract: The token is stored in a hidden input field named token.
    • JS Command: browser_eval("document.querySelector('input[name=token]').value").
  4. Form ID: Extract the form ID from the hidden field mw-wp-form-form-id.
    • JS Command: browser_eval("document.querySelector('input[name=\"mw-wp-form-form-id\"]').value").

5. Test Data Setup

  1. Create Form: Create a new mw-wp-form post.
    • wp post create --post_type=mw-wp-form --post_title="Vuln Form" --post_status=publish
  2. Configure Form:
    • Add a file field: [mwform_file name="upload_field"].
    • Enable Database storage: wp post meta update [FORM_ID] usedb 1.
  3. Publish Page: Place the form shortcode on a public page.
    • wp post create --post_type=page --post_title="Contact" --post_status=publish --post_content='[mwform_formkey key="[FORM_ID]"]'

6. Exploitation Strategy

Perform an unauthenticated HTTP POST request to the page where the form is hosted.

  • URL: http://localhost:8080/contact/
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • mw-wp-form-form-id: [FORM_ID]
    • token: [EXTRACTED_TOKEN]
    • mwf_upload_files[/var/www/html/]: wp-config.php
    • upload_field: (Leave empty, or provide dummy value if required by validation)

7. Expected Results

  1. The plugin will process the request and treat /var/www/html/wp-config.php as an uploaded attachment.
  2. The file wp-config.php will be moved (deleted from root, moved to uploads).
  3. The moved file will be located at: http://localhost:8080/wp-content/uploads/mw-wp-form_uploads/[PATH_TO_FILE]
    • The path typically follows the structure: mw-wp-form_uploads/[TOKEN]/[FORM_ID]/[ENCODED_KEY]/wp-config.php.
    • In version 5.0+, the filename might be renamed to something like /var/www/html/-wp-config.php.

8. Verification Steps

  1. Confirm Move: Use WP-CLI to check if the site is broken (since wp-config.php is gone).
    • ls /var/www/html/wp-config.php (Should return "No such file").
  2. Locate Target: Check the uploads directory for the moved file.
    • find /var/www/html/wp-content/uploads/mw-wp-form_uploads/ -name "*wp-config.php*"
  3. Read Secrets: Cat the moved file to prove it was successfully read/moved.

9. Alternative Approaches

If the absolute path /var/www/html/ is incorrect:

  • Use mwf_upload_files[../]=wp-config.php to attempt relative traversal if the temporary directory depth is known.
  • If wp-config.php is protected by rename() permissions, attempt to move /etc/passwd to a world-readable directory.
  • If the "complete" condition is hard to trigger, attempt to trigger the "confirm" condition (mwf_confirm_button=1), which also calls _file_upload() and might move files to a temporary location that can still be guessed.
Research Findings
Static analysis — not yet PoC-verified

Summary

MW WP Form is vulnerable to unauthenticated arbitrary file move because it improperly handles file path generation for uploaded attachments. By supplying an absolute path as a key in the `mwf_upload_files` parameter, an attacker can exploit the behavior of WordPress's `path_join()` to bypass intended directory restrictions and move sensitive files like `wp-config.php` into a publicly accessible directory.

Vulnerable Code

// classes/models/class.directory.php line 63
public static function generate_user_file_dirpath( $form_id, $name ) {
	$user_dir      = static::generate_user_dirpath( $form_id );
	$user_file_dir = path_join( $user_dir, $name );

	return $user_file_dir;
}

---

// classes/models/class.directory.php line 147
public static function generate_user_filepath( $form_id, $name, $filename ) {
	if ( ! $filename ) {
		return false;
	}

	$user_file_dir = static::generate_user_file_dirpath( $form_id, $name );
	if ( ! $user_file_dir || ! is_dir( $user_file_dir ) ) {
		return false;
	}
	// ... validation ...
	$filepath      = path_join( $user_file_dir, $filename );

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.1/classes/models/class.directory.php /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/models/class.directory.php
--- /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.1/classes/models/class.directory.php	2026-03-26 10:37:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mw-wp-form/5.1.2/classes/models/class.directory.php	2026-04-08 02:35:24.000000000 +0000
@@ -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;
 	}

Exploit Outline

The exploit targets forms that have at least one file upload field and the 'Saving inquiry data in database' option enabled. 1. Identify a target form and retrieve the required `mw-wp-form-form-id` and the CSRF `token` from the hidden input fields on the page. 2. Construct a POST request to the form's endpoint (the URL where the shortcode is rendered). 3. Inject a malicious absolute path via the `mwf_upload_files` array parameter. The key should be the absolute directory path of the target file, and the value should be the filename. For example: `mwf_upload_files[/var/www/html/]=wp-config.php`. 4. When the plugin processes the form, `generate_user_file_dirpath` uses `path_join()` with the absolute path key. Because `path_join()` returns absolute paths unchanged, the plugin treats the server's root or installation directory as the temporary upload folder. 5. The plugin validates that the directory exists and then identifies the target file (e.g., `wp-config.php`) as a 'surviving' attachment from a previous (simulated) step. 6. During the final processing stage (`_send`), the plugin calls `rename()` to move the target file from its original location into the public `wp-content/uploads/mw-wp-form_uploads/` directory. 7. The attacker can then access the moved file directly via its new URL to leak sensitive configuration data.

Check if your site is affected.

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