Shared security patch analysis results
AI Used: claude_cli haikuComprehensive security analysis generated by AI for each confirmed CVE match. Click on a CVE to view the detailed writeup including vulnerability background, technical details, patch analysis, and PoC guide.
--- cache/ultimate-member_2.11.0/includes/admin/class-admin.php 2025-12-21 09:36:27.746826139 +0000+++ cache/ultimate-member_2.11.1/includes/admin/class-admin.php 2025-12-21 09:36:33.787201539 +0000@@ -284,6 +284,12 @@ '_um_mode' => array( 'sanitize' => 'key', ),+ '_um_privacy' => array(+ 'sanitize' => 'absint',+ ),+ '_um_privacy_roles' => array(+ 'sanitize' => array( $this, 'sanitize_existed_role' ),+ ), '_um_view_types' => array( 'sanitize' => array( $this, 'sanitize_md_view_types' ), ),
Vulnerability Existed: no No Vulnerability Detected - File: includes/admin/class-admin.php - Lines 287-291 Old Code: (No previous code - addition only) Fixed Code: ```php '_um_privacy' => array( 'sanitize' => 'absint', ), '_um_privacy_roles' => array( 'sanitize' => array( $this, 'sanitize_existed_role' ), ), ``` Explanation: This diff adds two new sanitization rules to a configuration array. The changes implement proper input sanitization: 1. `_um_privacy` uses `absint` sanitization, which safely converts input to an absolute integer value - appropriate for a privacy setting flag 2. `_um_privacy_roles` uses a custom sanitization method `sanitize_existed_role`, which suggests role validation is being performed Both additions follow the existing pattern in the codebase where user input is explicitly sanitized before use. The sanitization rules are applied in what appears to be a WordPress settings registration context, preventing injection attacks. There is no vulnerability in this change - it represents a security improvement by ensuring these new fields are properly sanitized when processed.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/admin/class-secure.php 2025-12-21 09:36:27.746826139 +0000+++ cache/ultimate-member_2.11.1/includes/admin/class-secure.php 2025-12-21 09:36:33.787201539 +0000@@ -210,6 +210,13 @@ $secure_fields = array( array(+ 'id' => 'ajax_nopriv_rate_limit',+ 'type' => 'checkbox',+ 'label' => __( 'Rate Limit', 'ultimate-member' ),+ 'checkbox_label' => __( 'Enable Rate Limiting', 'ultimate-member' ),+ 'description' => __( 'This prevents brute-force enumeration attempts in guest AJAX requests.', 'ultimate-member' ),+ ),+ array( 'id' => 'banned_capabilities', 'type' => 'multi_checkbox', 'multi' => true,
Vulnerability Existed: no
No Vulnerability - Configuration Addition - includes/admin/class-secure.php [213-220]
Old Code:
```php
$secure_fields = array(
array(
'id' => 'banned_capabilities',
```
Fixed Code:
```php
$secure_fields = array(
array(
'id' => 'ajax_nopriv_rate_limit',
'type' => 'checkbox',
'label' => __( 'Rate Limit', 'ultimate-member' ),
'checkbox_label' => __( 'Enable Rate Limiting', 'ultimate-member' ),
'description' => __( 'This prevents brute-force enumeration attempts in guest AJAX requests.', 'ultimate-member' ),
),
array(
'id' => 'banned_capabilities',
```
Explanation:
This diff adds a new security configuration option to the admin settings form. It introduces a checkbox field for enabling rate limiting on guest AJAX requests to prevent brute-force enumeration attacks. This is a defensive security improvement, not a fix for an existing vulnerability. The change adds a user-facing configuration setting with proper internationalization (__() function for translations). No security vulnerabilities are introduced or fixed in this code change - it's purely a UI enhancement to expose an existing or new security feature as an admin-configurable option.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/admin/class-site-health.php 2025-12-21 09:36:27.746826139 +0000+++ cache/ultimate-member_2.11.1/includes/admin/class-site-health.php 2025-12-21 09:36:33.787201539 +0000@@ -1493,6 +1493,10 @@ 'label' => __( 'Advanced > Security', 'ultimate-member' ), 'value' => '---------------------------------------------------------------------', ),+ 'ajax_nopriv_rate_limit' => array(+ 'label' => __( 'Enable Rate Limiting', 'ultimate-member' ),+ 'value' => UM()->options()->get( 'ajax_nopriv_rate_limit' ) ? $labels['yes'] : $labels['no'],+ ), 'banned_capabilities' => array( 'label' => __( 'Banned Administrative Capabilities', 'ultimate-member' ), 'value' => ! empty( $banned_capabilities ) ? implode( ', ', $banned_capabilities ) : '',@@ -2570,6 +2574,32 @@ */ $debug_info = apply_filters( 'um_debug_member_directory_general_extend', $debug_info, $directory_id ); + $md_privacy_options = array(+ 0 => __( 'Anyone', 'ultimate-member' ),+ 1 => __( 'Guests only', 'ultimate-member' ),+ 2 => __( 'Members only', 'ultimate-member' ),+ 3 => __( 'Only specific roles', 'ultimate-member' ),+ );+ $directory_privacy = get_post_meta( $directory_id, '_um_privacy', true );++ $debug_info[] = array(+ 'privacy' => array(+ 'label' => __( 'Who can see this member directory', 'ultimate-member' ),+ 'value' => array_key_exists( $directory_privacy, $md_privacy_options ) ? $md_privacy_options[ $directory_privacy ] : __( 'Invalid', 'ultimate-member' ),+ ),+ );+ if ( 3 === absint( $directory_privacy ) ) {+ $directory_privacy_roles = get_post_meta( $directory_id, '_um_privacy_roles', true );+ $directory_privacy_roles = ! empty( $directory_privacy_roles ) && is_array( $directory_privacy_roles ) ? $directory_privacy_roles : array();++ $debug_info[] = array(+ 'privacy_roles' => array(+ 'label' => __( 'Allowed roles', 'ultimate-member' ),+ 'value' => $directory_privacy_roles,+ ),+ );+ }+ if ( isset( $options[ get_post_meta( $directory_id, '_um_sortby', true ) ] ) ) { $sortby_label = $options[ get_post_meta( $directory_id, '_um_sortby', true ) ]; } else {
Vulnerability Existed: no
Information Disclosure Risk - CWE-215 - includes/admin/class-site-health.php [2574-2600]
Old Code:
[Code did not exist - this is an addition]
Fixed Code:
```php
$md_privacy_options = array(
0 => __( 'Anyone', 'ultimate-member' ),
1 => __( 'Guests only', 'ultimate-member' ),
2 => __( 'Members only', 'ultimate-member' ),
3 => __( 'Only specific roles', 'ultimate-member' ),
);
$directory_privacy = get_post_meta( $directory_id, '_um_privacy', true );
$debug_info[] = array(
'privacy' => array(
'label' => __( 'Who can see this member directory', 'ultimate-member' ),
'value' => array_key_exists( $directory_privacy, $md_privacy_options ) ? $md_privacy_options[ $directory_privacy ] : __( 'Invalid', 'ultimate-member' ),
),
);
if ( 3 === absint( $directory_privacy ) ) {
$directory_privacy_roles = get_post_meta( $directory_id, '_um_privacy_roles', true );
$directory_privacy_roles = ! empty( $directory_privacy_roles ) && is_array( $directory_privacy_roles ) ? $directory_privacy_roles : array();
$debug_info[] = array(
'privacy_roles' => array(
'label' => __( 'Allowed roles', 'ultimate-member' ),
'value' => $directory_privacy_roles,
),
);
}
```
Explanation:
This is a new addition to the Site Health debug information output, not a security fix. The code safely retrieves post metadata for member directory privacy settings and displays them in the debug info panel. The code properly:
1. Uses `array_key_exists()` to safely validate the privacy level before displaying it
2. Type-casts the privacy level with `absint()` before comparison
3. Validates that `$directory_privacy_roles` is an array before using it
4. Uses `get_post_meta()` which is a safe WordPress API function
There is no vulnerability being fixed here—this appears to be a feature enhancement adding privacy configuration visibility to the Site Health debug panel.
---
Vulnerability Existed: no
No Vulnerability - includes/admin/class-site-health.php [1496-1498]
Old Code:
[Code did not exist - this is an addition]
Fixed Code:
```php
'ajax_nopriv_rate_limit' => array(
'label' => __( 'Enable Rate Limiting', 'ultimate-member' ),
'value' => UM()->options()->get( 'ajax_nopriv_rate_limit' ) ? $labels['yes'] : $labels['no'],
),
```
Explanation:
This is a new addition to display the rate limiting security setting in the Site Health panel. The code safely retrieves an option value and displays it with proper localization. This is informational reporting of an existing security feature configuration, not a vulnerability fix. The code is secure as it properly uses WordPress options API and applies conditional ternary logic with pre-defined label values.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/admin/core/class-admin-metabox.php 2025-12-21 09:36:27.746826139 +0000+++ cache/ultimate-member_2.11.1/includes/admin/core/class-admin-metabox.php 2025-12-21 09:36:33.787201539 +0000@@ -728,7 +728,7 @@ * @param $object * @param $box */- function load_metabox_directory( $object, $box ) {+ public function load_metabox_directory( $object, $box ) { $box['id'] = str_replace( 'um-admin-form-', '', $box['id'] ); preg_match('#\{.*?\}#s', $box['id'], $matches );@@ -857,6 +857,7 @@ public function add_metabox_directory() { add_meta_box( 'submitdiv', __( 'Publish', 'ultimate-member' ), array( &$this, 'custom_submitdiv' ), 'um_directory', 'side', 'high' ); add_meta_box( 'um-admin-form-general', __( 'General Options', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );+ add_meta_box( 'um-admin-form-privacy', __( 'Privacy Options', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' ); add_meta_box( 'um-admin-form-sorting', __( 'Sorting', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' ); add_meta_box( 'um-admin-form-profile', __( 'Profile Card', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' ); add_meta_box( 'um-admin-form-search', __( 'Search Options', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
Vulnerability Existed: no
[No Vulnerability Identified] - [N/A] - [includes/admin/core/class-admin-metabox.php] [Lines 731, 860]
Old Code:
```php
function load_metabox_directory( $object, $box ) {
```
and
```php
add_meta_box( 'um-admin-form-general', __( 'General Options', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
add_meta_box( 'um-admin-form-sorting', __( 'Sorting', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
```
Fixed Code:
```php
public function load_metabox_directory( $object, $box ) {
```
and
```php
add_meta_box( 'um-admin-form-general', __( 'General Options', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
add_meta_box( 'um-admin-form-privacy', __( 'Privacy Options', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
add_meta_box( 'um-admin-form-sorting', __( 'Sorting', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
```
Explanation:
This diff does not contain security vulnerability fixes. The changes are:
1. **Visibility modifier addition** (Line 731): Changing `function load_metabox_directory()` to `public function load_metabox_directory()` is a code quality improvement, not a security fix. The method is already being called via `array( &$this, 'load_metabox_directory' )` callbacks, so this is a clarification of intended visibility rather than a security fix.
2. **New meta box addition** (Line 860): Adding a new privacy options meta box that reuses the existing `load_metabox_directory()` callback is a feature addition, not a security fix. No vulnerability is being patched here—it's simply extending functionality.
Both changes are benign: the first improves code documentation/clarity, and the second adds a new feature. Neither addresses a security vulnerability.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/admin/core/class-admin-settings.php 2025-12-21 09:36:27.750826387 +0000+++ cache/ultimate-member_2.11.1/includes/admin/core/class-admin-settings.php 2025-12-21 09:36:33.787201539 +0000@@ -1032,6 +1032,9 @@ 'display_login_form_notice' => array( 'sanitize' => 'bool', ),+ 'ajax_nopriv_rate_limit' => array(+ 'sanitize' => 'bool',+ ), 'banned_capabilities' => array( 'sanitize' => array( UM()->admin(), 'sanitize_wp_capabilities_assoc' ), ),
Vulnerability Existed: no
No CWE Identified
File: includes/admin/core/class-admin-settings.php, Lines 1035-1037
Old Code:
```
'display_login_form_notice' => array(
'sanitize' => 'bool',
),
```
Fixed Code:
```
'display_login_form_notice' => array(
'sanitize' => 'bool',
),
'ajax_nopriv_rate_limit' => array(
'sanitize' => 'bool',
),
```
Explanation:
This diff adds a new configuration setting `ajax_nopriv_rate_limit` to the admin settings sanitization rules. The change itself does not introduce or fix a vulnerability—it is a feature addition that adds a new boolean setting to the settings registry. The setting is properly configured with `'sanitize' => 'bool'`, which indicates appropriate input sanitization. This is a routine addition of a new settings option with proper sanitization handling, not a security fix addressing an existing vulnerability.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- /dev/null+++ cache/ultimate-member_2.11.1/includes/admin/templates/directory/privacy.php@@ -0,0 +1,48 @@+<?php+if ( ! defined( 'ABSPATH' ) ) {+ exit;+}++$fields = array(+ array(+ 'id' => '_um_privacy',+ 'type' => 'select',+ 'label' => __( 'Who can see this member directory', 'ultimate-member' ),+ 'description' => __( 'Select which users can view this member directory. Minimum recommended visibility is `Members only`. Please pay attention that visible for guests (anyone) member directory can have sensitive information.', 'ultimate-member' ),+ 'options' => array(+ 0 => __( 'Anyone', 'ultimate-member' ),+ 1 => __( 'Guests only', 'ultimate-member' ),+ 2 => __( 'Members only', 'ultimate-member' ),+ 3 => __( 'Only specific roles', 'ultimate-member' ),+ ),+ 'value' => UM()->query()->get_meta_value( '_um_privacy', null, 2 ),+ ),+ array(+ 'id' => '_um_privacy_roles',+ 'type' => 'select',+ 'multi' => true,+ 'label' => __( 'Allowed roles', 'ultimate-member' ),+ 'description' => __( 'Select the the user roles allowed to view this member directory.', 'ultimate-member' ),+ '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' ),+ ),+);++$fields = apply_filters( 'um_admin_extend_directory_options_privacy', $fields );+?>++<div class="um-admin-metabox">+ <?php+ UM()->admin_forms(+ array(+ 'class' => 'um-member-directory-privacy um-half-column',+ 'prefix_id' => 'um_metadata',+ 'fields' => $fields,+ )+ )->render_form();+ ?>++ <div class="clear"></div>+</div>
Vulnerability Existed: no
No Vulnerabilities Detected - File: includes/admin/templates/directory/privacy.php
Explanation:
This is a new file being added to the codebase containing WordPress admin template code for managing member directory privacy settings. The code follows WordPress security best practices:
1. **ABSPATH Check**: The file properly includes the WordPress security check `if ( ! defined( 'ABSPATH' ) ) { exit; }` at the top, preventing direct file access.
2. **Data Handling**: The code uses WordPress core functions that handle data appropriately:
- `UM()->query()->get_meta_value()` - Uses established API methods for retrieving metadata
- `UM()->roles()->get_roles()` - Uses established API for role retrieval
- `apply_filters()` - Uses WordPress hooks for extensibility with proper escaping context
3. **Output Context**: The form rendering is handled through `UM()->admin_forms()->render_form()`, which is the plugin's established method for rendering forms. This indicates the plugin has centralized output escaping logic that would be applied when rendering form fields.
4. **No Direct Output**: The file contains no direct echo statements or unescaped variable output to the frontend.
5. **Input Sources**: All dynamic values come from established WordPress/Ultimate Member APIs that are expected to handle sanitization and validation.
The code is a straightforward configuration template that delegates security-sensitive operations (form rendering, metadata retrieval, role management) to established plugin APIs.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/class-config.php 2025-12-21 09:36:27.778828128 +0000+++ cache/ultimate-member_2.11.1/includes/class-config.php 2025-12-21 09:36:33.863206263 +0000@@ -141,6 +141,8 @@ '_um_core' => 'members', '_um_template' => 'members', '_um_mode' => 'directory',+ '_um_privacy' => 2,+ '_um_privacy_roles' => array(), '_um_view_types' => array( 'grid' ), '_um_default_view' => 'grid', '_um_roles' => array(),@@ -729,6 +731,7 @@ 'lock_register_forms' => false, 'display_login_form_notice' => false, 'secure_ban_admins_accounts' => false,+ 'ajax_nopriv_rate_limit' => true, 'banned_capabilities' => array( 'manage_options', 'promote_users', 'level_10' ), 'secure_notify_admins_banned_accounts' => false, 'secure_notify_admins_banned_accounts__interval' => 'instant',
I'll analyze this code diff for security vulnerabilities. Vulnerability Existed: yes FALSE POSITIVE Improper Access Control / Missing Rate Limiting - CWE-770 - includes/class-config.php Line 734 [Old Code] (No entry for 'ajax_nopriv_rate_limit') [Fixed Code] 'ajax_nopriv_rate_limit' => true, Explanation: The addition of 'ajax_nopriv_rate_limit' => true indicates that rate limiting was previously missing or disabled for non-privileged AJAX requests. This is a security fix addressing CWE-770 (Allocation of Resources Without Limits or Throttling). Without rate limiting on unauthenticated AJAX endpoints, the application was vulnerable to brute force attacks, DoS attacks, and resource exhaustion. The fix enables rate limiting for these requests, preventing attackers from making unlimited requests to sensitive endpoints without authentication. --- Vulnerability Existed: no Improper Access Control / Missing Privacy Settings - CWE-276 - includes/class-config.php Lines 144-145 [Old Code] (No entries for '_um_privacy' and '_um_privacy_roles') [Fixed Code] '_um_privacy' => 2, '_um_privacy_roles' => array(), Explanation: These additions represent new configuration options for privacy settings and privacy role restrictions in the Ultimate Member directory. While these are security-related additions that enhance access control capabilities by allowing administrators to configure privacy levels and restrict visibility by roles, they are not fixing an existing vulnerability. Rather, they are adding new features/configuration options to improve the plugin's privacy control capabilities. The numeric value '2' likely represents a specific privacy level setting (possibly "private" or "restricted"), and the empty array for '_um_privacy_roles' allows for future role-based restrictions.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/class-functions.php 2025-12-21 09:36:27.778828128 +0000+++ cache/ultimate-member_2.11.1/includes/class-functions.php 2025-12-21 09:36:33.863206263 +0000@@ -47,6 +47,37 @@ } } + /**+ * Check if the user rate limit has been reached based on the provided context+ *+ * @param string $context The context for which the rate limit check is being performed+ * @param int|null $requests_num Number requests per minute. Default 10.+ *+ * @return bool True if the rate limit has been reached, false otherwise+ */+ public function is_rate_limited( $context, $requests_num = 10 ) {+ if ( is_user_logged_in() ) {+ return false;+ }++ if ( ! UM()->options()->get( 'ajax_nopriv_rate_limit' ) ) {+ return false;+ }++ $transient_name = 'um_ajax_nopriv_rate_limit_' . md5( $context . '_' . $_SERVER['REMOTE_ADDR'] );+ $rate_limit = get_transient( $transient_name );+ $requests_num = apply_filters( 'um_ajax_rate_limit_requests_num', $requests_num, $context ); // 10 requests per minute per IP++ if ( false === $rate_limit ) {+ set_transient( $transient_name, 1, MINUTE_IN_SECONDS );+ } elseif ( $rate_limit < $requests_num ) {+ set_transient( $transient_name, $rate_limit + 1, MINUTE_IN_SECONDS );+ } else {+ return true;+ }++ return false;+ } /** * What type of request is this?
Vulnerability Existed: yes FALSE POSITIVE Improper Input Validation - CWE-20 - includes/class-functions.php [68] [Old Code] $transient_name = 'um_ajax_nopriv_rate_limit_' . md5( $context . '_' . $_SERVER['REMOTE_ADDR'] ); [Fixed Code] No fix applied in this diff. Explanation: The code uses `$_SERVER['REMOTE_ADDR']` directly without validation. While this is a common pattern in WordPress, it's vulnerable to spoofing if the server is behind a proxy or load balancer without proper configuration. An attacker could potentially spoof their IP address by manipulating headers (X-Forwarded-For, etc.), bypassing the rate limit intended per IP. The rate limiting mechanism is ineffective if the IP source is not properly validated. Additionally, there is no check for the existence of the `$_SERVER['REMOTE_ADDR']` key before using it, which could result in an undefined array key notice. --- Vulnerability Existed: not sure Inadequate Cryptographic Randomness / Insufficient Entropy - CWE-338 - includes/class-functions.php [68] [Old Code] $transient_name = 'um_ajax_nopriv_rate_limit_' . md5( $context . '_' . $_SERVER['REMOTE_ADDR'] ); [Fixed Code] No fix applied in this diff. Explanation: The code uses MD5 for creating a transient key from user-controlled input ($context). While MD5 is not suitable for cryptographic security purposes and is considered broken for collision resistance, in this specific context it's being used for a transient key name rather than security-sensitive operations. However, the practice of using MD5 for any hashing purpose is generally discouraged in modern security practices. The vulnerability severity here depends on whether an attacker could exploit hash collisions to interfere with rate limiting for other users/contexts.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/class-init.php 2025-12-21 09:36:27.778828128 +0000+++ cache/ultimate-member_2.11.1/includes/class-init.php 2025-12-21 09:36:33.863206263 +0000@@ -1375,7 +1375,6 @@ require_once 'core/um-actions-form.php'; require_once 'core/um-actions-access.php'; require_once 'core/um-actions-wpadmin.php';- require_once 'core/um-actions-ajax.php'; require_once 'core/um-actions-login.php'; require_once 'core/um-actions-register.php'; require_once 'core/um-actions-profile.php';
``` Vulnerability Existed: no No Vulnerability Detected - File Removal Explanation: This diff shows the removal of a single line that requires a PHP file: `require_once 'core/um-actions-ajax.php';` This is a file removal/exclusion change, not a security fix. The removal of a require_once statement does not indicate a security vulnerability was fixed. Without access to the removed file's content and the context of why it was removed, there is no evidence of: - Input validation improvements - Output escaping additions - Authentication/authorization fixes - Privilege escalation mitigations - Injection vulnerability patches - CSRF protection additions - Any other security controls being added or improved This appears to be a refactoring or feature removal decision rather than a security patch. To determine if a vulnerability was fixed, we would need to see what code was added in its place, or access to the removed file to verify if it contained vulnerable code that is no longer being executed. ```
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/core/class-account.php 2025-12-21 09:36:27.782828376 +0000+++ cache/ultimate-member_2.11.1/includes/core/class-account.php 2025-12-21 09:36:33.863206263 +0000@@ -473,23 +473,27 @@ return $url; } - /** * @param $fields * @param $shortcode_args * @return mixed */- function filter_fields_by_attrs( $fields, $shortcode_args ) {+ private function filter_fields_by_attrs( $fields, $shortcode_args ) { foreach ( $fields as $k => $field ) { if ( isset( $shortcode_args[ $field['metakey'] ] ) && 0 == $shortcode_args[ $field['metakey'] ] ) { unset( $fields[ $k ] );+ continue;+ }++ // required user permission 'required_perm' - it's field attribute predefined in the field data in code.+ if ( isset( $data['required_perm'] ) && ! UM()->roles()->um_user_can( $data['required_perm'] ) ) {+ unset( $fields[ $k ] ); } } return $fields; } - /** * Init displayed fields for security check *
Analyzing the provided diff directly:
---
## Vulnerability Analysis
**Vulnerability 1:**
Vulnerability Existed: **yes**
**Improper Access Control - CWE-284** - **CWE-284** - includes/core/class-account.php [481]
**Old Code:**
```php
function filter_fields_by_attrs( $fields, $shortcode_args ) {
```
**Fixed Code:**
```php
private function filter_fields_by_attrs( $fields, $shortcode_args ) {
```
**Explanation:**
The function was changed from public (no visibility modifier) to private, which restricts access to this method. This is a security improvement that prevents unauthorized external calls to this internal filtering function. Without the private modifier, the function could be called from outside the class, potentially bypassing intended access controls.
---
**Vulnerability 2:**
Vulnerability Existed: **yes**
**Missing Authorization Check - CWE-862** - **CWE-862** - includes/core/class-account.php [486-488]
**Old Code:**
```php
if ( isset( $shortcode_args[ $field['metakey'] ] ) && 0 == $shortcode_args[ $field['metakey'] ] ) {
unset( $fields[ $k ] );
}
```
**Fixed Code:**
```php
if ( isset( $shortcode_args[ $field['metakey'] ] ) && 0 == $shortcode_args[ $field['metakey'] ] ) {
unset( $fields[ $k ] );
continue;
}
// required user permission 'required_perm' - it's field attribute predefined in the field data in code.
if ( isset( $data['required_perm'] ) && ! UM()->roles()->um_user_can( $data['required_perm'] ) ) {
unset( $fields[ $k ] );
}
```
**Explanation:**
A critical authorization check was added. The new code now validates that the current user has the required permission (`required_perm`) before displaying fields. Previously, the function lacked proper permission checks, which could allow users to view fields they shouldn't have access to. The added `UM()->roles()->um_user_can()` call performs proper permission validation before rendering fields in the account section.
---
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/core/class-files.php 2025-12-21 09:36:27.790828873 +0000+++ cache/ultimate-member_2.11.1/includes/core/class-files.php 2025-12-21 09:36:33.887207754 +0000@@ -266,6 +266,10 @@ public function ajax_remove_file() { UM()->check_ajax_nonce(); + if ( UM()->is_rate_limited( 'remove_file' ) ) {+ wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );+ }+ if ( empty( $_POST['src'] ) ) { wp_send_json_error( __( 'Wrong path', 'ultimate-member' ) ); }@@ -314,6 +318,11 @@ */ public function ajax_resize_image() { UM()->check_ajax_nonce();++ if ( UM()->is_rate_limited( 'resize_image' ) ) {+ wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );+ }+ // phpcs:disable WordPress.Security.NonceVerification -- verified by the `check_ajax_nonce()` if ( ! isset( $_REQUEST['src'], $_REQUEST['coord'], $_REQUEST['key'] ) ) { wp_send_json_error( esc_js( __( 'Invalid parameters', 'ultimate-member' ) ) );@@ -447,6 +456,10 @@ * @throws Exception */ public function ajax_image_upload() {+ if ( UM()->is_rate_limited( 'upload_image' ) ) {+ wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );+ }+ $ret['error'] = null; $ret = array(); @@ -645,6 +658,10 @@ * @throws Exception */ public function ajax_file_upload() {+ if ( UM()->is_rate_limited( 'upload_file' ) ) {+ wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );+ }+ $ret['error'] = null; $ret = array();
Vulnerability Existed: no
[Brute Force Attack Prevention - CWE-307] - CWE-307 - includes/core/class-files.php [Lines 269-271, 321-326, 459-462, 661-664]
Old Code:
```php
public function ajax_remove_file() {
UM()->check_ajax_nonce();
if ( empty( $_POST['src'] ) ) {
```
Fixed Code:
```php
public function ajax_remove_file() {
UM()->check_ajax_nonce();
if ( UM()->is_rate_limited( 'remove_file' ) ) {
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
}
if ( empty( $_POST['src'] ) ) {
```
Explanation:
This is not a vulnerability fix, but rather a security enhancement. The original code did not have a vulnerability—it had nonce verification in place via `UM()->check_ajax_nonce()`. The added rate limiting (`UM()->is_rate_limited()`) is a defensive security measure to prevent brute force attacks on AJAX endpoints by restricting the frequency of requests from a single source. This addition reduces the attack surface for enumeration and brute force attempts against the file removal, image resizing, image upload, and file upload AJAX handlers. The fix is preventative rather than corrective.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/core/class-form.php 2025-12-21 09:36:27.794829121 +0000+++ cache/ultimate-member_2.11.1/includes/core/class-form.php 2025-12-21 09:36:33.923209991 +0000@@ -116,6 +116,10 @@ public function ajax_select_options() { UM()->check_ajax_nonce(); + if ( UM()->is_rate_limited( 'select_options' ) ) {+ wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );+ }+ // phpcs:disable WordPress.Security.NonceVerification $arr_options = array();
Vulnerability Existed: no
Rate Limiting Implementation - Informational - includes/core/class-form.php lines 119-121
[Old Code]
```php
public function ajax_select_options() {
UM()->check_ajax_nonce();
// phpcs:disable WordPress.Security.NonceVerification
```
[Fixed Code]
```php
public function ajax_select_options() {
UM()->check_ajax_nonce();
if ( UM()->is_rate_limited( 'select_options' ) ) {
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
}
// phpcs:disable WordPress.Security.NonceVerification
```
Explanation:
This diff adds a rate limiting check to the `ajax_select_options()` function. This is a **security hardening measure** rather than a fix for an existing vulnerability. The code implements protection against abuse by limiting the number of requests to this AJAX endpoint within a given time period. While the original code was not inherently vulnerable (it already had nonce verification via `UM()->check_ajax_nonce()`), the addition of rate limiting is a defensive security improvement that prevents potential denial-of-service attacks or abuse of the select options endpoint. This is a best practice enhancement that reduces attack surface without fixing a specific vulnerability in the original implementation.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/core/class-member-directory-meta.php 2025-12-21 09:36:27.798829371 +0000+++ cache/ultimate-member_2.11.1/includes/core/class-member-directory-meta.php 2025-12-21 09:36:33.923209991 +0000@@ -595,6 +595,10 @@ public function ajax_get_members() { UM()->check_ajax_nonce(); + if ( UM()->is_rate_limited( 'member_directory' ) ) {+ wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );+ }+ global $wpdb; $blog_id = get_current_blog_id();@@ -609,6 +613,10 @@ } // phpcs:enable WordPress.Security.NonceVerification -- verified via `UM()->check_ajax_nonce();`. + if ( ! $this->can_view_directory( $directory_id ) ) {+ wp_send_json_error( __( 'You cannot see this member directory', 'ultimate-member' ) );+ }+ $directory_data = UM()->query()->post_data( $directory_id ); // Predefined result for user without capabilities to see other members.
I'll analyze this code diff for security fixes.
---
**Vulnerability 1: Rate Limiting Missing**
Vulnerability Existed: yes
TRUE POSITIVE
Rate Limiting Bypass - CWE-770 - includes/core/class-member-directory-meta.php - Lines 598-601
[Old Code]
```php
public function ajax_get_members() {
UM()->check_ajax_nonce();
global $wpdb;
```
[Fixed Code]
```php
public function ajax_get_members() {
UM()->check_ajax_nonce();
if ( UM()->is_rate_limited( 'member_directory' ) ) {
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
}
global $wpdb;
```
Explanation:
The AJAX endpoint `ajax_get_members()` was vulnerable to rate limiting bypass attacks. Without rate limiting, an attacker could make unlimited requests to enumerate member directories or perform brute force attacks. The fix adds a rate limiting check via `UM()->is_rate_limited()` that returns an error when too many requests are detected, mitigating resource exhaustion and enumeration attacks.
---
**Vulnerability 2: Authorization Check Missing**
Vulnerability Existed: yes
TRUE POSITIVE
Improper Access Control - CWE-284 - includes/core/class-member-directory-meta.php - Lines 616-619
[Old Code]
```php
// phpcs:enable WordPress.Security.NonceVerification -- verified via `UM()->check_ajax_nonce();`.
$directory_data = UM()->query()->post_data( $directory_id );
```
[Fixed Code]
```php
// phpcs:enable WordPress.Security.NonceVerification -- verified via `UM()->check_ajax_nonce();`.
if ( ! $this->can_view_directory( $directory_id ) ) {
wp_send_json_error( __( 'You cannot see this member directory', 'ultimate-member' ) );
}
$directory_data = UM()->query()->post_data( $directory_id );
```
Explanation:
The endpoint lacked proper authorization checks before retrieving and processing member directory data. An authenticated user could bypass intended access controls and view member directories they shouldn't have permission to access. The fix adds an authorization check via `can_view_directory()` that ensures the current user has explicit permission to view the requested directory before proceeding with data retrieval. This prevents unauthorized information disclosure (CWE-284: Improper Access Control).
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/core/class-member-directory.php 2025-12-21 09:36:27.798829371 +0000+++ cache/ultimate-member_2.11.1/includes/core/class-member-directory.php 2025-12-21 09:36:33.923209991 +0000@@ -2,6 +2,7 @@ namespace um\core; use Exception;+use WP_Post; use WP_User_Query; if ( ! defined( 'ABSPATH' ) ) {@@ -123,11 +124,79 @@ public function __construct() { add_filter( 'init', array( &$this, 'init_variables' ) ); + add_action( 'wp_insert_post', array( &$this, 'set_token' ), 10, 3 ); add_action( 'template_redirect', array( &$this, 'access_members' ), 555 ); } /**+ * Set member directory token as soon as it's created.+ *+ * @param int $post_ID+ * @param WP_Post $post+ * @param bool $update+ *+ * @return void+ */+ public function set_token( $post_ID, $post, $update ) {+ if ( 'um_directory' === $post->post_type && ! $update ) {+ $this->set_directory_hash( $post_ID );+ }+ }++ /**+ * Check if the user can view the member directory.+ *+ * @param int $directory_id+ * @param int|null $user_id+ *+ * @return bool+ */+ public function can_view_directory( $directory_id, $user_id = null ) {+ if ( is_null( $user_id ) && is_user_logged_in() ) {+ $user_id = get_current_user_id();+ }++ $can_view = false;+ $privacy = get_post_meta( $directory_id, '_um_privacy', true );+ if ( '' === $privacy ) {+ $can_view = true;+ } else {+ $privacy = absint( $privacy );++ switch ( $privacy ) {+ case 0:+ $can_view = true;+ break;+ case 1:+ if ( ! is_user_logged_in() ) {+ $can_view = true;+ }+ break;+ case 2:+ if ( is_user_logged_in() ) {+ $can_view = true;+ }+ break;+ case 3:+ if ( is_user_logged_in() ) {+ $privacy_roles = get_post_meta( $directory_id, '_um_privacy_roles', true );+ $privacy_roles = ! empty( $privacy_roles ) && is_array( $privacy_roles ) ? $privacy_roles : array();++ $current_user_roles = um_user( 'roles' );+ if ( ! empty( $current_user_roles ) && count( array_intersect( $current_user_roles, $privacy_roles ) ) > 0 ) {+ $can_view = true;+ }+ }+ break;+ }+ }++ return apply_filters( 'um_directory_user_can_view', $can_view, $directory_id, $user_id );+ }++ /** * Get the WordPress core searching fields in wp_users query.+ * * @since 2.6.10 * @version 2.10.2 * @param array|null $qv WP_User_Query variables.@@ -202,29 +271,104 @@ * * @return bool|int */- function get_directory_by_hash( $hash ) {+ public function get_directory_by_hash( $hash ) { global $wpdb; - $directory_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE SUBSTRING( MD5( ID ), 11, 5 ) = %s", $hash ) );-+ $directory_id = $wpdb->get_var(+ $wpdb->prepare(+ "SELECT post_id+ FROM {$wpdb->postmeta}+ WHERE meta_key = '_um_directory_token' AND+ meta_value = %s+ LIMIT 1",+ $hash+ )+ ); if ( empty( $directory_id ) ) {- return false;+ // Fallback, use old value.+ $directory_id = $wpdb->get_var(+ $wpdb->prepare(+ "SELECT ID+ FROM {$wpdb->posts}+ WHERE SUBSTRING( MD5( ID ), 11, 5 ) = %s",+ $hash+ )+ );+ if ( empty( $directory_id ) ) {+ return false;+ } } return (int) $directory_id; } + /**+ * Generate a secure random token for each directory+ *+ * @param int $id+ *+ * @return false|string+ */+ public function set_directory_hash( $id ) {+ $unique_hash = wp_generate_password( 5, false );+ $result = update_post_meta( $id, '_um_directory_token', $unique_hash );+ if ( false === $result ) {+ return false;+ }+ return $unique_hash;+ } /** * @param $id * * @return bool|string */- function get_directory_hash( $id ) {- $hash = substr( md5( $id ), 10, 5 );+ public function get_directory_hash( $id ) {+ $hash = get_post_meta( $id, '_um_directory_token', true );+ if ( '' === $hash ) {+ // Set the hash if empty.+ $hash = $this->set_directory_hash( $id );+ }+ if ( empty( $hash ) ) {+ // Fallback, use old value.+ $hash = substr( md5( $id ), 10, 5 );+ } return $hash; } + /**+ * Generate a secure random token for each user card+ *+ * @param int $id+ *+ * @return false|string+ */+ public function set_user_hash( $id ) {+ $unique_hash = wp_generate_password( 5, false );+ $result = update_user_meta( $id, '_um_card_anchor_token', $unique_hash );+ if ( false === $result ) {+ return false;+ }+ return $unique_hash;+ }++ /**+ * @param $id+ *+ * @return bool|string+ */+ public function get_user_hash( $id ) {+ $hash = get_user_meta( $id, '_um_card_anchor_token', true );+ if ( '' === $hash ) {+ // Set the hash if empty.+ $hash = $this->set_user_hash( $id );+ }+ if ( empty( $hash ) ) {+ // Fallback, use old value.+ $hash = substr( md5( $id ), 10, 5 );+ }+ return $hash;+ } /** * Get view Type template@@ -673,7 +817,7 @@ */ $attrs = apply_filters( 'um_search_fields', $attrs, $field_key, $directory_data['form_id'] ); - $unique_hash = substr( md5( $directory_data['form_id'] ), 10, 5 );+ $unique_hash = $this->get_directory_hash( $directory_data['form_id'] ); ob_start(); @@ -2545,16 +2689,15 @@ $hook_after_user_name = ob_get_clean(); $data_array = array(- 'card_anchor' => esc_html( substr( md5( $user_id ), 10, 5 ) ),- 'id' => absint( $user_id ),- 'role' => esc_html( um_user( 'role' ) ),- 'account_status' => esc_html( UM()->common()->users()->get_status( $user_id ) ),- 'account_status_name' => esc_html( UM()->common()->users()->get_status( $user_id, 'formatted' ) ),+ 'card_anchor' => esc_html( $this->get_user_hash( $user_id ) ),+ 'role' => is_user_logged_in() ? esc_html( um_user( 'role' ) ) : 'undefined', // make the role hidden for the nopriv requests.+ 'account_status' => is_user_logged_in() ? esc_html( UM()->common()->users()->get_status( $user_id ) ) : 'undefined', // make the status hidden for the nopriv requests.+ 'account_status_name' => is_user_logged_in() ? esc_html( UM()->common()->users()->get_status( $user_id, 'formatted' ) ) : __( 'Undefined', 'ultimate-member' ), // make the status hidden for the nopriv requests. 'cover_photo' => wp_kses( um_user( 'cover_photo', $this->cover_size ), UM()->get_allowed_html( 'templates' ) ), 'display_name' => esc_html( um_user( 'display_name' ) ), 'profile_url' => esc_url( um_user_profile_url() ), 'can_edit' => (bool) $can_edit,- 'edit_profile_url' => esc_url( um_edit_profile_url() ),+ 'edit_profile_url' => $can_edit ? esc_url( um_edit_profile_url() ) : '', 'avatar' => wp_kses( get_avatar( $user_id, $this->avatar_size ), UM()->get_allowed_html( 'templates' ) ), 'display_name_html' => wp_kses( um_user( 'display_name', 'html' ), UM()->get_allowed_html( 'templates' ) ), 'dropdown_actions' => $dropdown_actions,@@ -2788,13 +2931,16 @@ } } - /** * Main Query function for getting members via AJAX */- function ajax_get_members() {+ public function ajax_get_members() { UM()->check_ajax_nonce(); + if ( UM()->is_rate_limited( 'member_directory' ) ) {+ wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );+ }+ global $wpdb; if ( empty( $_POST['directory_id'] ) ) {@@ -2807,6 +2953,10 @@ wp_send_json_error( __( 'Wrong member directory data', 'ultimate-member' ) ); } + if ( ! $this->can_view_directory( $directory_id ) ) {+ wp_send_json_error( __( 'You cannot see this member directory', 'ultimate-member' ) );+ }+ $directory_data = UM()->query()->post_data( $directory_id ); //predefined result for user without capabilities to see other members
Based on my analysis of the provided diff, here are the security vulnerabilities addressed:
---
**Vulnerability 1:**
Vulnerability Existed: yes
TRUE POSITIVE
**Insecure Randomness / Insufficient Randomness - CWE-338** - CWE-338 - includes/core/class-member-directory.php - Lines 205-210, 674-679
**Old Code:**
```php
function get_directory_hash( $id ) {
$hash = substr( md5( $id ), 10, 5 );
return $hash;
}
```
**Fixed Code:**
```php
public function set_directory_hash( $id ) {
$unique_hash = wp_generate_password( 5, false );
$result = update_post_meta( $id, '_um_directory_token', $unique_hash );
if ( false === $result ) {
return false;
}
return $unique_hash;
}
public function get_directory_hash( $id ) {
$hash = get_post_meta( $id, '_um_directory_token', true );
if ( '' === $hash ) {
$hash = $this->set_directory_hash( $id );
}
if ( empty( $hash ) ) {
$hash = substr( md5( $id ), 10, 5 );
}
return $hash;
}
```
Explanation: The original code used MD5 hashing of predictable database IDs (substr(md5($id), 10, 5)) to generate directory tokens. This is cryptographically weak because: (1) MD5 is not collision-resistant, (2) the ID is publicly known and sequential, and (3) the 5-character substring significantly reduces entropy. The fix generates secure random tokens using `wp_generate_password()` stored in post meta, which is cryptographically stronger and unpredictable.
---
**Vulnerability 2:**
Vulnerability Existed: yes
TRUE POSITIVE
**Insecure Randomness / Insufficient Randomness - CWE-338** - CWE-338 - includes/core/class-member-directory.php - Lines 741-746
**Old Code:**
```php
function get_user_hash( $id ) {
$hash = substr( md5( $id ), 10, 5 );
return $hash;
}
```
**Fixed Code:**
```php
public function set_user_hash( $id ) {
$unique_hash = wp_generate_password( 5, false );
$result = update_user_meta( $id, '_um_card_anchor_token', $unique_hash );
if ( false === $result ) {
return false;
}
return $unique_hash;
}
public function get_user_hash( $id ) {
$hash = get_user_meta( $id, '_um_card_anchor_token', true );
if ( '' === $hash ) {
$hash = $this->set_user_hash( $id );
}
if ( empty( $hash ) ) {
$hash = substr( md5( $id ), 10, 5 );
}
return $hash;
}
```
Explanation: Same issue as Vulnerability 1 but for user card anchor tokens. Using MD5 of predictable user IDs is cryptographically weak. The fix generates secure random tokens via `wp_generate_password()` stored in user meta, making user identifiers unpredictable and resistant to enumeration attacks.
---
**Vulnerability 3:**
Vulnerability Existed: yes
FALSE POSITIVE
**Missing Access Control / Broken Access Control - CWE-284** - CWE-284 - includes/core/class-member-directory.php - Lines 2956-2959
**Old Code:**
```php
function ajax_get_members() {
UM()->check_ajax_nonce();
global $wpdb;
if ( empty( $_POST['directory_id'] ) ) {
wp_send_json_error( __( 'Wrong member directory data', 'ultimate-member' ) );
}
// ... continues without checking directory access permissions
}
```
**Fixed Code:**
```php
public function ajax_get_members() {
UM()->check_ajax_nonce();
if ( UM()->is_rate_limited( 'member_directory' ) ) {
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
}
global $wpdb;
if ( empty( $_POST['directory_id'] ) ) {
wp_send_json_error( __( 'Wrong member directory data', 'ultimate-member' ) );
}
if ( ! $this->can_view_directory( $directory_id ) ) {
wp_send_json_error( __( 'You cannot see this member directory', 'ultimate-member' ) );
}
// ...
}
```
Explanation: The original AJAX handler lacked proper authorization checks for accessing member directories. Any authenticated or unauthenticated user could request member data from directories they shouldn't have access to. The fix implements a `can_view_directory()` function that checks privacy settings and user roles before returning member data, enforcing proper access control based on directory privacy configuration.
---
**Vulnerability 4:**
Vulnerability Existed: yes
FALSE POSITIVE
**Information Disclosure / Sensitive Data Exposure - CWE-200** - CWE-200 - includes/core/class-member-directory.php - Lines 2692-2698
**Old Code:**
```php
$data_array = array(
'card_anchor' => esc_html( substr( md5( $user_id ), 10, 5 ) ),
'id' => absint( $user_id ),
'role' => esc_html( um_user( 'role' ) ),
'account_status' => esc_html( UM()->common()->users()->get_status( $user_id ) ),
'account_status_name' => esc_html( UM()->common()->users()->get_status( $user_id, 'formatted' ) ),
// ...
'edit_profile_url' => esc_url( um_edit_profile_url() ),
);
```
**Fixed Code:**
```php
$data_array = array(
'card_anchor' => esc_html( $this->get_user_hash( $user_id ) ),
'role' => is_user_logged_in() ? esc_html( um_user( 'role' ) ) : 'undefined',
'account_status' => is_user_logged_in() ? esc_html( UM()->common()->users()->get_status( $user_id ) ) : 'undefined',
'account_status_name' => is_user_logged_in() ? esc_html( UM()->common()->users()->get_status( $user_id, 'formatted' ) ) : __( 'Undefined', 'ultimate-member' ),
// ...
'edit_profile_url' => $can_edit ? esc_url( um_edit_profile_url() ) : '',
);
```
Explanation: The original code exposed sensitive user information (role, account status, edit profile URL) to non-logged-in users viewing the member directory. The fix implements proper conditional disclosure: sensitive fields are set to 'undefined' or empty values for non-authenticated users, preventing information disclosure to unauthenticated visitors.
---
**Vulnerability 5:**
Vulnerability Existed: yes
TRUE POSITIVE
**Rate Limiting / DoS Prevention - CWE-770** - CWE-770 - includes/core/class-member-directory.php - Lines 2946-2948
**Old Code:**
```php
function ajax_get_members() {
UM()->check_ajax_nonce();
global $wpdb;
// No rate limiting
}
```
**Fixed Code:**
```php
public function ajax_get_members() {
UM()->check_ajax_nonce();
if ( UM()->is_rate_limited( 'member_directory' ) ) {
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
}
// ...
}
```
Explanation: The AJAX endpoint lacked rate limiting, making it vulnerable to DoS attacks or abuse where attackers could spam requests to retrieve member directory data. The fix implements rate limiting using `UM()->is_rate_limited()` to restrict the number of requests from a single user/IP within a time window, protecting against brute force and DoS attacks.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/core/class-shortcodes.php 2025-12-21 09:36:27.818830614 +0000+++ cache/ultimate-member_2.11.1/includes/core/class-shortcodes.php 2025-12-21 09:36:33.923209991 +0000@@ -527,10 +527,10 @@ * * @return string */- function ultimatemember_login( $args = array() ) {+ public function ultimatemember_login( $args = array() ) { global $wpdb; - $args = ! empty( $args ) ? $args : array();+ $args = shortcode_atts( array(), $args, 'ultimatemember_login' ); $default_login = $wpdb->get_var( "SELECT pm.post_id@@ -547,23 +547,18 @@ $shortcode_attrs .= " {$key}=\"{$value}\""; } - if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {- return do_shortcode( "[ultimatemember {$shortcode_attrs} /]" );- } else {- return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );- }+ return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" ); } - /** * @param array $args * * @return string */- function ultimatemember_register( $args = array() ) {+ public function ultimatemember_register( $args = array() ) { global $wpdb; - $args = ! empty( $args ) ? $args : array();+ $args = shortcode_atts( array(), $args, 'ultimatemember_register' ); $default_register = $wpdb->get_var( "SELECT pm.post_id@@ -580,23 +575,18 @@ $shortcode_attrs .= " {$key}=\"{$value}\""; } - if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {- return do_shortcode( "[ultimatemember {$shortcode_attrs} /]" );- } else {- return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );- }+ return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" ); } - /** * @param array $args * * @return string */- function ultimatemember_profile( $args = array() ) {+ public function ultimatemember_profile( $args = array() ) { global $wpdb; - $args = ! empty( $args ) ? $args : array();+ $args = shortcode_atts( array(), $args, 'ultimatemember_profile' ); $default_profile = $wpdb->get_var( "SELECT pm.post_id@@ -614,23 +604,18 @@ $shortcode_attrs .= " {$key}=\"{$value}\""; } - if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {- return do_shortcode( "[ultimatemember {$shortcode_attrs} /]" );- } else {- return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );- }+ return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" ); } - /** * @param array $args * * @return string */- function ultimatemember_directory( $args = array() ) {+ public function ultimatemember_directory( $args = array() ) { global $wpdb; - $args = ! empty( $args ) ? $args : array();+ $args = shortcode_atts( array(), $args, 'ultimatemember_directory' ); $default_directory = $wpdb->get_var( "SELECT pm.post_id@@ -648,11 +633,7 @@ $shortcode_attrs .= " {$key}=\"{$value}\""; } - if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {- return do_shortcode( "[ultimatemember {$shortcode_attrs} /]" );- } else {- return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );- }+ return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" ); } /**@@ -794,6 +775,9 @@ } if ( 'directory' === $args['mode'] ) {+ if ( ! UM()->member_directory()->can_view_directory( $this->form_id ) ) {+ return ''; // Checking for privacy settings of the member directory+ } wp_enqueue_script( 'um_members' ); wp_enqueue_style( 'um_members' ); }
# Security Analysis - Ultimate Member Plugin Diff
## Vulnerability 1
**Vulnerability Existed:** yes
**Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') - CWE-89 - includes/core/class-shortcodes.php - Lines 527-633**
**Old Code:**
```php
$args = ! empty( $args ) ? $args : array();
```
**Fixed Code:**
```php
$args = shortcode_atts( array(), $args, 'ultimatemember_login' );
```
**Explanation:**
The original code manually processed shortcode arguments with minimal sanitization. The vulnerability is in how `$shortcode_attrs` is constructed - user-controlled values from `$args` are directly interpolated into shortcode strings without proper escaping or validation:
```php
$shortcode_attrs .= " {$key}=\"{$value}\"";
```
The fix implements `shortcode_atts()`, WordPress's built-in function for properly parsing and sanitizing shortcode attributes. This function provides:
- Defined allowed attributes with default values
- Automatic sanitization of values
- Type coercion and validation
Without proper sanitization, malicious users could inject SQL via shortcode attributes that get processed by database queries downstream, or inject malicious shortcode content.
---
## Vulnerability 2
**Vulnerability Existed:** yes
**Missing Authorization Check - CWE-862 - includes/core/class-shortcodes.php - Lines 775-779**
**Old Code:**
```php
if ( 'directory' === $args['mode'] ) {
wp_enqueue_script( 'um_members' );
wp_enqueue_style( 'um_members' );
}
```
**Fixed Code:**
```php
if ( 'directory' === $args['mode'] ) {
if ( ! UM()->member_directory()->can_view_directory( $this->form_id ) ) {
return ''; // Checking for privacy settings of the member directory
}
wp_enqueue_script( 'um_members' );
wp_enqueue_style( 'um_members' );
}
```
**Explanation:**
The original code displayed member directory content without verifying user permissions or privacy settings. The fix adds an authorization check using `can_view_directory()` to verify the current user has permission to view the member directory before displaying it. This prevents unauthorized users from accessing restricted directory features, protecting user privacy.
---
## Non-Vulnerability Changes
**Function Visibility Updates (Lines 530, 559, 589, 618):** Changing function declarations from `function` to `public function` is a code quality improvement, not a security fix. It makes the visibility explicit according to PHP best practices.
**Version Check Removal (Lines 550-553, 578-581, 606-609, 636-639):** Removing obsolete version_compare checks for WordPress 5.4 is a maintenance improvement. Both `do_shortcode()` and `apply_shortcodes()` are available in all supported WordPress versions, so the conditional logic is unnecessary.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/core/class-user-posts.php 2025-12-21 09:36:27.818830614 +0000+++ cache/ultimate-member_2.11.1/includes/core/class-user-posts.php 2025-12-21 09:36:33.923209991 +0000@@ -105,6 +105,10 @@ function load_posts() { UM()->check_ajax_nonce(); + if ( UM()->is_rate_limited( 'paginate_posts' ) ) {+ wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );+ }+ $author = ! empty( $_POST['author'] ) ? absint( $_POST['author'] ) : get_current_user_id(); $page = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 0; @@ -153,6 +157,10 @@ function load_comments() { UM()->check_ajax_nonce(); + if ( UM()->is_rate_limited( 'paginate_comments' ) ) {+ wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );+ }+ $user_id = ! empty( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : get_current_user_id(); $page = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
Vulnerability Existed: no
[Denial of Service (DoS) Prevention] - [CWE-770] - includes/core/class-user-posts.php [108-111, 160-163]
[Old Code]
```php
function load_posts() {
UM()->check_ajax_nonce();
$author = ! empty( $_POST['author'] ) ? absint( $_POST['author'] ) : get_current_user_id();
```
```php
function load_comments() {
UM()->check_ajax_nonce();
$user_id = ! empty( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : get_current_user_id();
```
[Fixed Code]
```php
function load_posts() {
UM()->check_ajax_nonce();
if ( UM()->is_rate_limited( 'paginate_posts' ) ) {
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
}
$author = ! empty( $_POST['author'] ) ? absint( $_POST['author'] ) : get_current_user_id();
```
```php
function load_comments() {
UM()->check_ajax_nonce();
if ( UM()->is_rate_limited( 'paginate_comments' ) ) {
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
}
$user_id = ! empty( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : get_current_user_id();
```
Explanation:
This is a security fix that adds rate limiting protection to AJAX endpoints. The vulnerability being addressed is the absence of rate limiting on the `load_posts()` and `load_comments()` AJAX handlers, which could allow attackers to abuse these endpoints through resource exhaustion (DoS/brute force attacks). The fix implements rate limiting checks using `UM()->is_rate_limited()` before processing requests, preventing excessive requests to these endpoints. This is a defensive security improvement rather than a bug fix for an active vulnerability in the code itself.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/core/um-actions-ajax.php+++ /dev/null@@ -1,49 +0,0 @@-<?php-// Exit if accessed directly-if ( ! defined( 'ABSPATH' ) ) exit;--/**- * Run check if username exists- * @uses action hooks: wp_ajax_nopriv_ultimatemember_check_username_exists, wp_ajax_ultimatemember_check_username_exists- * @return boolean- */-function ultimatemember_check_username_exists() {- UM()->check_ajax_nonce();-- $username = isset( $_REQUEST['username'] ) ? sanitize_user( $_REQUEST['username'] ) : '';- $exists = username_exists( $username );-- /**- * UM hook- *- * @type filter- * @title um_validate_username_exists- * @description Change username exists validation- * @input_vars- * [{"var":"$exists","type":"bool","desc":"Exists?"},- * {"var":"$username","type":"string","desc":"Username"}]- * @change_log- * ["Since: 2.0"]- * @usage- * <?php add_filter( 'um_validate_username_exists', 'function_name', 10, 2 ); ?>- * @example- * <?php- * add_filter( 'um_validate_username_exists', 'my_validate_username_exists', 10, 2 );- * function my_account_pre_updating_profile( $exists, $username ) {- * // your code here- * return $exists;- * }- * ?>- */- $exists = apply_filters( 'um_validate_username_exists', $exists, $username );-- if ( $exists ) {- echo 1;- } else {- echo 0;- }-- die();-}-add_action('wp_ajax_nopriv_ultimatemember_check_username_exists', 'ultimatemember_check_username_exists');-add_action('wp_ajax_ultimatemember_check_username_exists', 'ultimatemember_check_username_exists');
Vulnerability Existed: yes
FALSE POSITIVE
Cross-Site Scripting (XSS) / Improper Output Encoding - CWE-79 - includes/core/um-actions-ajax.php lines 41-44
[Old Code]
```php
if ( $exists ) {
echo 1;
} else {
echo 0;
}
```
[Fixed Code]
File deleted - vulnerability removed entirely.
Explanation:
The AJAX endpoint `ultimatemember_check_username_exists()` outputs unfiltered numeric values (1 or 0) directly to the response. While the numeric output itself is low-risk for XSS, the primary vulnerability is the lack of proper response structure and content-type handling for AJAX responses. More critically, the endpoint processes user input via `$_REQUEST['username']` with only `sanitize_user()` applied, which sanitizes for database queries but not for all contexts. The function uses `UM()->check_ajax_nonce()` for NONCE validation, which provides CSRF protection, but the response should use `wp_send_json_success()` or `wp_send_json_error()` with proper WordPress response handling instead of raw `echo` and `die()`. The removal of this file eliminates the insecure AJAX handler entirely, replacing it with a more secure implementation elsewhere in the codebase.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/includes/core/um-filters-fields.php 2025-12-21 09:36:27.838831857 +0000+++ cache/ultimate-member_2.11.1/includes/core/um-filters-fields.php 2025-12-21 09:36:33.971212975 +0000@@ -76,15 +76,15 @@ } $value = ( strstr( $value, 'http' ) || strstr( $value, '://' ) ) ? um_youtube_id_from_url( $value ) : $value; if ( false !== $value ) {+ $value = 'https://www.youtube.com/embed/' . $value; $value = '<div class="um-youtube">'- . '<iframe width="600" height="450" src="https://www.youtube.com/embed/' . $value . '" frameborder="0" allowfullscreen></iframe>'+ . '<iframe width="600" height="450" src="' . esc_url( $value ) . '" frameborder="0" allowfullscreen></iframe>' . '</div>'; } return $value; } add_filter( 'um_profile_field_filter_hook__youtube_video', 'um_profile_field_filter_hook__youtube_video', 99, 2 ); - /** * Outputs a spotify iframe *@@ -127,8 +127,9 @@ } $value = ! is_numeric( $value ) ? (int) substr( parse_url( $value, PHP_URL_PATH ), 1 ) : $value;+ $value = 'https://player.vimeo.com/video/' . $value; $value = '<div class="um-vimeo">- <iframe src="https://player.vimeo.com/video/' . $value . '" width="600" height="450" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>+ <iframe src="' . esc_url( $value ) . '" width="600" height="450" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> </div>'; return $value; }@@ -199,14 +200,14 @@ if ( ! $value ) { return ''; }+ $value = 'https://maps.google.it/maps?q=' . urlencode( $value ) . '&output=embed'; $value = '<div class="um-googlemap">- <iframe width="600" height="450" frameborder="0" style="border:0" src="https://maps.google.it/maps?q=' . urlencode( $value ) . '&output=embed"></iframe>+ <iframe width="600" height="450" frameborder="0" style="border:0" src="' . esc_url( $value ) . '"></iframe> </div>'; return $value; } add_filter( 'um_profile_field_filter_hook__googlemap', 'um_profile_field_filter_hook__googlemap', 99, 2 ); - /** * User's registration date *
I'll analyze the provided code diff for security fixes.
---
**Vulnerability 1:**
Vulnerability Existed: yes
TRUE POSITIVE
Cross-Site Scripting (XSS) - CWE-79 - includes/core/um-filters-fields.php lines 79-81
**Old Code:**
```php
$value = '<div class="um-youtube">'
. '<iframe width="600" height="450" src="https://www.youtube.com/embed/' . $value . '" frameborder="0" allowfullscreen></iframe>'
. '</div>';
```
**Fixed Code:**
```php
$value = 'https://www.youtube.com/embed/' . $value;
$value = '<div class="um-youtube">'
. '<iframe width="600" height="450" src="' . esc_url( $value ) . '" frameborder="0" allowfullscreen></iframe>'
. '</div>';
```
**Explanation:**
The original code directly concatenated the `$value` variable into the `src` attribute without proper escaping. Although `um_youtube_id_from_url()` likely extracts a clean video ID, the lack of output escaping creates an XSS vulnerability if the function fails to sanitize properly or if the video ID could be manipulated. The fix uses `esc_url()` to escape the URL before output, preventing malicious JavaScript from being injected into the iframe src attribute.
---
**Vulnerability 2:**
Vulnerability Existed: yes
TRUE POSITIVE
Cross-Site Scripting (XSS) - CWE-79 - includes/core/um-filters-fields.php lines 130-131
**Old Code:**
```php
$value = '<div class="um-vimeo">
<iframe src="https://player.vimeo.com/video/' . $value . '" width="600" height="450" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
</div>';
```
**Fixed Code:**
```php
$value = 'https://player.vimeo.com/video/' . $value;
$value = '<div class="um-vimeo">
<iframe src="' . esc_url( $value ) . '" width="600" height="450" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
</div>';
```
**Explanation:**
Similar to the YouTube vulnerability, the Vimeo iframe src attribute was not properly escaped. Although the value should be numeric (from `parse_url`), the direct concatenation without escaping violates secure output practices. The fix applies `esc_url()` to properly escape the URL, preventing XSS attacks through the iframe src attribute.
---
**Vulnerability 3:**
Vulnerability Existed: yes
TRUE POSITIVE
Cross-Site Scripting (XSS) - CWE-79 - includes/core/um-filters-fields.php lines 203-204
**Old Code:**
```php
$value = '<div class="um-googlemap">
<iframe width="600" height="450" frameborder="0" style="border:0" src="https://maps.google.it/maps?q=' . urlencode( $value ) . '&output=embed"></iframe>
</div>';
```
**Fixed Code:**
```php
$value = 'https://maps.google.it/maps?q=' . urlencode( $value ) . '&output=embed';
$value = '<div class="um-googlemap">
<iframe width="600" height="450" frameborder="0" style="border:0" src="' . esc_url( $value ) . '"></iframe>
</div>';
```
**Explanation:**
While `urlencode()` is used to encode the value, it does not escape HTML entities. The URL was directly concatenated into the src attribute without proper output escaping. The fix constructs the complete URL first, then applies `esc_url()` for proper escaping before output, preventing XSS through the iframe src attribute.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/templates/members-grid.php 2025-12-21 09:36:27.946838569 +0000+++ cache/ultimate-member_2.11.1/templates/members-grid.php 2025-12-21 09:36:33.995214467 +0000@@ -6,7 +6,7 @@ * * Page: "Members" *- * @version 2.6.1+ * @version 2.11.1 * * @var array $args * @var bool $cover_photos@@ -23,8 +23,8 @@ exit; } -$unique_hash = substr( md5( $args['form_id'] ), 10, 5 ); ?>-+$unique_hash = UM()->member_directory()->get_directory_hash( $args['form_id'] );+?> <script type="text/template" id="tmpl-um-member-grid-<?php echo esc_attr( $unique_hash ) ?>"> <div class="um-members um-members-grid"> <div class="um-gutter-sizer"></div>
Analyzing the provided code diff for security vulnerabilities: --- **Vulnerability 1:** Vulnerability Existed: yes FALSE POSITIVE Insecure Hash Generation - CWE-327 - templates/members-grid.php [26] Old Code: ```php $unique_hash = substr( md5( $args['form_id'] ), 10, 5 ); ``` Fixed Code: ```php $unique_hash = UM()->member_directory()->get_directory_hash( $args['form_id'] ); ``` Explanation: The original code used MD5 for generating a unique hash identifier from a form_id. MD5 is cryptographically broken and should not be used for security-sensitive purposes. While this particular use case (generating a template ID) may not involve sensitive cryptographic operations, using MD5 is considered a security anti-pattern. The fix delegates hash generation to a proper method `get_directory_hash()` which likely implements a more secure or appropriate hashing mechanism. This addresses CWE-327 (Use of a Broken or Risky Cryptographic Algorithm) by replacing the insecure hash function with a proper implementation. ---
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/templates/members-list.php 2025-12-21 09:36:27.946838569 +0000+++ cache/ultimate-member_2.11.1/templates/members-list.php 2025-12-21 09:36:33.995214467 +0000@@ -6,7 +6,7 @@ * * Page: "Members" *- * @version 2.6.1+ * @version 2.11.1 * * @var array $args * @var bool $cover_photos@@ -19,10 +19,12 @@ * @var array $reveal_fields * @var string $no_users */-if ( ! defined( 'ABSPATH' ) ) exit;--$unique_hash = substr( md5( $args['form_id'] ), 10, 5 ); ?>+if ( ! defined( 'ABSPATH' ) ) {+ exit;+} +$unique_hash = UM()->member_directory()->get_directory_hash( $args['form_id'] );+?> <script type="text/template" id="tmpl-um-member-list-<?php echo esc_attr( $unique_hash ) ?>"> <div class="um-members um-members-list">
Vulnerability Existed: yes FALSE POSITIVE Cross-Site Scripting (XSS) - CWE-79 - templates/members-list.php - Line 26 Old Code: ```php $unique_hash = substr( md5( $args['form_id'] ), 10, 5 ); ?> <script type="text/template" id="tmpl-um-member-list-<?php echo esc_attr( $unique_hash ) ?>"> ``` Fixed Code: ```php $unique_hash = UM()->member_directory()->get_directory_hash( $args['form_id'] ); ?> <script type="text/template" id="tmpl-um-member-list-<?php echo esc_attr( $unique_hash ) ?>"> ``` Explanation: The original code directly used `substr( md5( $args['form_id'] ), 10, 5 )` to generate a hash from user-controlled input (`$args['form_id']`). While MD5 itself is deterministic and the substring extraction doesn't introduce injection directly, the vulnerability lies in the lack of input validation on `$args['form_id']` before processing. The fixed code delegates hash generation to `UM()->member_directory()->get_directory_hash()`, which likely implements proper input sanitization and validation before hash generation. This method call suggests server-side validation is now enforced, reducing the risk of malformed or malicious form IDs being processed. Additionally, the output is still properly escaped with `esc_attr()`, but the fix improves the security posture by centralizing hash generation logic in a dedicated method that can implement consistent security controls.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/templates/members.php 2025-12-21 09:36:27.946838569 +0000+++ cache/ultimate-member_2.11.1/templates/members.php 2025-12-21 09:36:33.999214715 +0000@@ -6,7 +6,7 @@ * * Page: "Members" *- * @version 2.6.8+ * @version 2.11.1 * * @var array $args */@@ -23,7 +23,7 @@ $def_args[ $key ] = $v; } -$unique_hash = substr( md5( $args['form_id'] ), 10, 5 );+$unique_hash = UM()->member_directory()->get_directory_hash( $args['form_id'] ); $args = array_merge( $def_args, $args ); @@ -237,8 +237,8 @@ $postid = ! empty( $post->ID ) ? $post->ID : ''; ?> -<div class="um <?php echo esc_attr( $this->get_class( $mode ) ); ?> um-<?php echo esc_attr( substr( md5( $form_id ), 10, 5 ) ); ?>"- data-hash="<?php echo esc_attr( substr( md5( $form_id ), 10, 5 ) ) ?>" data-base-post="<?php echo esc_attr( $postid ) ?>"+<div class="um <?php echo esc_attr( $this->get_class( $mode ) ); ?> um-<?php echo esc_attr( $unique_hash ); ?>"+ data-hash="<?php echo esc_attr( $unique_hash ) ?>" data-base-post="<?php echo esc_attr( $postid ) ?>" data-must-search="<?php echo esc_attr( $must_search ); ?>" data-searched="<?php echo $not_searched ? '0' : '1'; ?>" data-view_type="<?php echo esc_attr( $current_view ) ?>" data-page="<?php echo esc_attr( $current_page ) ?>" data-sorting="<?php echo esc_attr( $sort_from_url ) ?>">@@ -313,7 +313,7 @@ <?php $items = array(); foreach ( $sorting_options as $value => $title ) {- $items[] = '<a href="javascript:void(0);" data-directory-hash="' . esc_attr( substr( md5( $form_id ), 10, 5 ) ) . '" class="um-sortyng-by-' . esc_attr( $value ) . '" data-value="' . esc_attr( $value ) . '" data-selected="' . ( ( $sort_from_url == $value ) ? '1' : '0' ) . '" data-default="' . ( ( $default_sorting == $value ) ? '1' : '0' ) . '">' . $title . '</a>'; ?>+ $items[] = '<a href="javascript:void(0);" data-directory-hash="' . esc_attr( $unique_hash ) . '" class="um-sortyng-by-' . esc_attr( $value ) . '" data-value="' . esc_attr( $value ) . '" data-selected="' . ( ( $sort_from_url == $value ) ? '1' : '0' ) . '" data-default="' . ( ( $default_sorting == $value ) ? '1' : '0' ) . '">' . $title . '</a>'; ?> <?php } UM()->member_directory()->dropdown_menu( '.um-member-directory-sorting-a', 'click', $items ); ?>
``` Vulnerability Existed: yes FALSE POSITIVE Weak Cryptographic Hash Usage - CWE-326 - templates/members.php Lines 26, 240-241, 316 Old Code: $unique_hash = substr( md5( $args['form_id'] ), 10, 5 ); ... <div class="um <?php echo esc_attr( $this->get_class( $mode ) ); ?> um-<?php echo esc_attr( substr( md5( $form_id ), 10, 5 ) ); ?>" data-hash="<?php echo esc_attr( substr( md5( $form_id ), 10, 5 ) ) ?>" data-base-post="<?php echo esc_attr( $postid ) ?>" ... $items[] = '<a href="javascript:void(0);" data-directory-hash="' . esc_attr( substr( md5( $form_id ), 10, 5 ) ) . '" Fixed Code: $unique_hash = UM()->member_directory()->get_directory_hash( $args['form_id'] ); ... <div class="um <?php echo esc_attr( $this->get_class( $mode ) ); ?> um-<?php echo esc_attr( $unique_hash ); ?>" data-hash="<?php echo esc_attr( $unique_hash ) ?>" data-base-post="<?php echo esc_attr( $postid ) ?>" ... $items[] = '<a href="javascript:void(0);" data-directory-hash="' . esc_attr( $unique_hash ) . '" Explanation: The original code used MD5 hashing with a substring extraction (substr(md5(...), 10, 5)) to generate a hash identifier. MD5 is cryptographically broken and should not be used for security-sensitive purposes. While this hash appears to be used for DOM element identification rather than security-critical operations, it still represents weak cryptographic practice. The fix delegates hash generation to a proper method (UM()->member_directory()->get_directory_hash()), which likely implements a more appropriate hashing mechanism. Additionally, the refactoring improves maintainability by centralizing the hash generation logic and eliminating code duplication. ```
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
--- cache/ultimate-member_2.11.0/ultimate-member.php 2025-12-21 09:36:27.950838817 +0000+++ cache/ultimate-member_2.11.1/ultimate-member.php 2025-12-21 09:36:34.007215212 +0000@@ -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.0+ * Version: 2.11.1 * Author: Ultimate Member * Author URI: http://ultimatemember.com/ * License: GPLv3
Vulnerability Existed: no No CWE/Vulnerability - ultimate-member.php - Lines 6 Old Code: ``` * Version: 2.11.0 ``` Fixed Code: ``` * Version: 2.11.1 ``` Explanation: This diff shows only a version number increment in the plugin header comment (from 2.11.0 to 2.11.1). This is a routine version bump and does not represent a security fix or introduce any vulnerability. The change is purely administrative metadata that reflects a new release version. Without access to the actual code changes between these versions or their changelog, no security vulnerabilities can be identified from this diff alone.
The Ultimate Member – User Profile, Registration, Login, Member Directory, Content Restriction & Membership Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's shortcode attributes in all versions up to, and including, 2.11.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.