CVE-2026-49043

WP Migrate Lite – Migration Made Easy <= 2.7.8 - Cross-Site Request Forgery

mediumCross-Site Request Forgery (CSRF)
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.7.9
Patched in
6d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.7.8
PublishedJune 10, 2026
Last updatedJune 15, 2026
Affected pluginwp-migrate-db

What Changed in the Fix

Changes introduced in v2.7.9

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

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()` 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_download function, which is triggered when the download parameter 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

  1. Entry Point: The plugin registers various hooks
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.8/class/Common/Filesystem/Filesystem.php /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.9/class/Common/Filesystem/Filesystem.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.8/class/Common/Filesystem/Filesystem.php	2023-05-18 13:24:40.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.9/class/Common/Filesystem/Filesystem.php	2026-06-02 13:47:12.000000000 +0000
@@ -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'));
         }
     }
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.8/class/Common/Http/Http.php /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.9/class/Common/Http/Http.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.8/class/Common/Http/Http.php	2024-02-08 19:11:30.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.9/class/Common/Http/Http.php	2026-06-02 13:47:12.000000000 +0000
@@ -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();
     }
 }
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.8/class/Common/Migration/MigrationHelper.php /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.9/class/Common/Migration/MigrationHelper.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.8/class/Common/Migration/MigrationHelper.php	2023-06-01 17:02:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-migrate-db/2.7.9/class/Common/Migration/MigrationHelper.php	2026-06-02 13:47:12.000000000 +0000
@@ -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.