MW WP Form <= 5.1.1 - Unauthenticated Arbitrary File Move via regenerate_upload_file_keys
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:HTechnical Details
What Changed in the Fix
Changes introduced in v5.1.2
Source Code
WordPress.org SVNThis 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
mwformshortcode is rendered. - Hook:
template_redirect(viaMW_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:
- A form must exist with at least one file upload field.
- The "Saving inquiry data in database" option must be enabled for that form.
- The attacker must know the absolute path to the target file (standard WordPress installs use
/var/www/html/).
3. Code Flow
- Request Entry:
MW_WP_Form_Main_Controller::_template_redirect()(Line 121,class.main.php) is called. - 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$_POSTdata (including the maliciousmwf_upload_filesarray) into the$this->variablesproperty. - Condition Check: If
$_POST['token']is provided and no buttons are pressed,get_post_condition()returns'complete'. - File Processing:
_template_redirectcalls_file_upload()(Line 160,class.main.php), which callsMWF_Functions::regenerate_upload_file_keys(). - Path Resolution:
regenerate_upload_file_keyscallsMW_WP_Form_Directory::generate_user_filepath($form_id, $name, $filename). - Traversal Sink:
generate_user_file_dirpath($form_id, $name)(Line 64,class.directory.php) callspath_join($user_dir, $name). If$nameis/var/www/html/, it returns/var/www/html/. - Validation Bypass:
generate_user_filepath(Line 150) checksis_dir($user_file_dir). Since/var/www/html/exists, it proceeds. The final path becomes/var/www/html/wp-config.php. - The Move:
MW_WP_Form_Main_Controller::_send()(Line 245,class.main.php) identifies that database saving is enabled (usedb). It callsMWF_Functions::move_temp_file_to_upload_dir(), which executesrename('/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.
- Identify Form: Find a page containing a form generated by the plugin.
- Navigate: Use
browser_navigateto that page. - Extract: The token is stored in a hidden input field named
token.- JS Command:
browser_eval("document.querySelector('input[name=token]').value").
- JS Command:
- 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").
- JS Command:
5. Test Data Setup
- Create Form: Create a new
mw-wp-formpost.wp post create --post_type=mw-wp-form --post_title="Vuln Form" --post_status=publish
- Configure Form:
- Add a file field:
[mwform_file name="upload_field"]. - Enable Database storage:
wp post meta update [FORM_ID] usedb 1.
- Add a file field:
- 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.phpupload_field: (Leave empty, or provide dummy value if required by validation)
7. Expected Results
- The plugin will process the request and treat
/var/www/html/wp-config.phpas an uploaded attachment. - The file
wp-config.phpwill be moved (deleted from root, moved to uploads). - 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.
- The path typically follows the structure:
8. Verification Steps
- Confirm Move: Use WP-CLI to check if the site is broken (since
wp-config.phpis gone).ls /var/www/html/wp-config.php(Should return "No such file").
- 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*"
- 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.phpto attempt relative traversal if the temporary directory depth is known. - If
wp-config.phpis protected byrename()permissions, attempt to move/etc/passwdto 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.
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
@@ -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.