WP Migrate Lite – Migration Made Easy <= 2.7.8 - Cross-Site Request Forgery
Description
The WP Migrate Lite – Migration Made Easy plugin for WordPress is vulnerable to Cross-Site Request Forgery in versions up to, and including, 2.7.8. This is due to missing or incorrect nonce validation on a function. This makes it possible for unauthenticated attackers to perform an unauthorized action via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v2.7.9
Source Code
WordPress.org SVNresult ### 1. Vulnerability Summary The **WP Migrate Lite** plugin (up to version 2.7.8) is vulnerable to **Cross-Site Request Forgery (CSRF)** due to missing or inadequate nonce validation on administrative actions. Specifically, the plugin implements a mechanism in `Http::http_verify_download()` t…
Show full research plan
result
1. Vulnerability Summary
The WP Migrate Lite plugin (up to version 2.7.8) is vulnerable to Cross-Site Request Forgery (CSRF) due to missing or inadequate nonce validation on administrative actions. Specifically, the plugin implements a mechanism in Http::http_verify_download() that triggers file operations via the Filesystem class without verifying a WordPress nonce.
While the plugin generates secure URLs for some actions (like diagnostic log downloads in MigrationHelper.php), it maintains an alternative, unprotected path in Http::http_verify_download() that responds to the download GET parameter. An attacker can trick a logged-in administrator into visiting a crafted URL, causing the server to execute filesystem actions (like generating or downloading migration files) without the administrator's intent.
2. Attack Vector Analysis
- Vulnerable Endpoint:
wp-admin/admin-ajax.php(or any admin page if the hook is generic). - Vulnerable Action: The
http_verify_downloadfunction, which is triggered when thedownloadparameter is present in a request. - HTTP Parameter:
download(GET). - Authentication Level: Requires an authenticated administrator session (targeted via CSRF).
- Preconditions: The victim must be logged in as an administrator and click a malicious link or visit a page controlled by the attacker.
3. Code Flow
- Entry Point: The plugin registers various hooks
Summary
The WP Migrate Lite plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) due to a lack of nonce validation in the http_verify_download function. This allows unauthenticated attackers to trigger sensitive filesystem actions, such as downloading migration files or diagnostic logs, by tricking a logged-in administrator into clicking a malicious link.
Vulnerable Code
// class/Common/Http/Http.php line 179 /** * Check for download * if found prepare file for download * * @return void */ function http_verify_download() { if (!empty($_GET['download'])) { $this->filesystem->download_file(); } } --- // class/Common/Migration/MigrationHelper.php line 103 'this_website_name' => sanitize_title_with_dashes(DB_NAME), 'this_download_url' => network_admin_url($this->props->plugin_base . '&download='), 'this_prefix' => $site_details['prefix'], // TODO: Remove backwards compatibility.
Security Fix
@@ -785,12 +785,26 @@ $util->set_time_limit(); $raw_dump_name = htmlspecialchars($_GET['download'], ENT_QUOTES | ENT_HTML5); + // Strip any path components to prevent directory traversal + $raw_dump_name = basename($raw_dump_name); $dump_name = $table_helper->format_dump_name($raw_dump_name); $diskfile = $this->get_upload_info('path') . DIRECTORY_SEPARATOR . $dump_name; if ($is_full_site_export) { $diskfile = $this->get_upload_info('path') . DIRECTORY_SEPARATOR . $raw_dump_name . '.zip'; } + // Verify the resolved path is within the plugin's upload directory + $upload_path = realpath($this->get_upload_info('path')); + if ($upload_path === false) { + wp_die(__('Upload directory not found.', 'wp-migrate-db')); + } + + $resolved_diskfile = realpath($diskfile); + if ($resolved_diskfile === false || + strpos($resolved_diskfile, $upload_path . DIRECTORY_SEPARATOR) !== 0) { + wp_die(__('Invalid file path.', 'wp-migrate-db')); + } + $filename = basename($diskfile); $last_dash = strrpos($filename, '-'); $salt = substr($filename, $last_dash, 6); @@ -827,7 +841,7 @@ wp_die(sprintf(__('<h3>Output prevented download. </h3> %s', 'wp-migrate-db'), $msg)); } } else { - wp_die(__('Could not find the file to download:', 'wp-migrate-db') . '<br>' . esc_html($diskfile)); + wp_die(__('Could not find the file to download.', 'wp-migrate-db')); } } @@ -179,8 +179,22 @@ */ function http_verify_download() { - if (!empty($_GET['download'])) { - $this->filesystem->download_file(); + if (empty($_GET['download'])) { + return; } + + // Verify user has appropriate capability + $cap = is_multisite() ? 'manage_network_options' : 'export'; + if (!current_user_can($cap)) { + wp_die(__('You do not have permission to download files.', 'wp-migrate-db')); + } + + // Verify nonce to prevent CSRF + $nonce = isset($_GET['nonce']) ? $_GET['nonce'] : ''; + if (!wp_verify_nonce($nonce, 'wpmdb-download')) { + wp_die(__('Security check failed. Please refresh the page and try again.', 'wp-migrate-db')); + } + + $this->filesystem->download_file(); } } @@ -100,7 +100,7 @@ 'this_uploads_dir' => $site_details['uploads_dir'], // TODO: Remove backwards compatibility. 'this_plugin_url' => trailingslashit(plugins_url($this->props->plugin_folder_name)), 'this_website_name' => sanitize_title_with_dashes(DB_NAME), - 'this_download_url' => network_admin_url($this->props->plugin_base . '&download='), + 'this_download_url' => network_admin_url($this->props->plugin_base . '&nonce=' . Util::create_nonce('wpmdb-download') . '&download='), 'this_prefix' => $site_details['prefix'], // TODO: Remove backwards compatibility. 'this_temp_prefix' => $this->props->temp_prefix, 'this_plugin_base' => esc_html($this->props->plugin_base),
Exploit Outline
The exploit targets the unprotected `http_verify_download` function in the plugin's HTTP handler. An attacker crafts a URL targeting a WordPress administrator, appended with the `download` GET parameter (e.g., `wp-admin/tools.php?page=wp-migrate-db&download=backup_filename`). Since the plugin does not verify nonces or administrative capabilities before calling the file download method, the administrator's browser will execute the file operation automatically if they visit the link while authenticated. This can be used to exfiltrate database backups or diagnostic logs.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.