[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f80g06jywMnFsjjwb5VfwEfgSt-dt2piTcBxLEmdpniE":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-32424","sprout-clients-authenticated-contributor-stored-cross-site-scripting-2","Sprout Clients \u003C= 3.2.2 - Authenticated (Contributor+) Stored Cross-Site Scripting","The Sprout Clients plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 3.2.2 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.","sprout-clients",null,"\u003C=3.2.2","3.2.3","medium",6.4,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-02-27 00:00:00","2026-04-15 21:17:22",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F097760a6-0b8b-437d-a6ad-29675cbb5091?source=api-prod",48,[22,23,24,25,26,27],"changelog.txt","controllers\u002Fclients\u002FClients_AJAX.php","controllers\u002Fclients\u002FClients_Admin_Meta_Boxes.php","controllers\u002Fclients\u002FClients_Users.php","readme.txt","sprout-clients.php","researched",false,3,"# Exploitation Research Plan - CVE-2026-32424 (Sprout Clients)\n\n## 1. Vulnerability Summary\nThe **Sprout Clients** plugin for WordPress (versions \u003C= 3.2.2) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin fails to sanitize user input before storing it in user metadata and subsequently fails to escape that data when rendering it in the WordPress admin interface. \n\nSpecifically, the `SC_Users::save_profile_fields` function in `controllers\u002Fclients\u002FClients_Users.php` updates several user meta fields (`sc_dob`, `sc_phone`, `sc_twitter`, `sc_linkedin`, `sc_note`) directly from the `$_POST` array without using sanitization functions like `sanitize_text_field`. When an administrator or another privileged user views the affected user's profile, these fields are rendered without proper escaping, leading to script execution.\n\n## 2. Attack Vector Analysis\n*   **Endpoint:** `wp-admin\u002Fprofile.php` (for the Contributor to inject) and `wp-admin\u002Fuser-edit.php` (for the Admin to trigger).\n*   **Authentication:** Contributor level or higher is required. A Contributor can edit their own profile, which is sufficient to trigger the vulnerability.\n*   **Vulnerable Parameters:** `sc_twitter`, `sc_phone`, `sc_linkedin`, `sc_dob`, `sc_note`.\n*   **Preconditions:** The Sprout Clients plugin must be active.\n\n## 3. Code Flow\n1.  **Entry Point:** The plugin registers hooks to handle profile updates in `SC_Users::init()`:\n    *   `add_action( 'personal_options_update', array( __CLASS__, 'save_profile_fields' ) );`\n    *   `add_action( 'edit_user_profile_update', array( __CLASS__, 'save_profile_fields' ) );`\n2.  **Storage (Sink):** When a Contributor saves their profile, `SC_Users::save_profile_fields($user_id)` is called.\n    *   It checks `current_user_can( 'edit_user', $user_id )`, which is true for a user editing themselves.\n    *   It executes: `update_user_meta( $user_id, self::TWITTER, $_POST['sc_twitter'] );` (where `self::TWITTER` is `'sc_twitter'`).\n    *   No sanitization (e.g., `sanitize_text_field`) is applied to `$_POST['sc_twitter']`.\n3.  **Rendering (Source):** When an Admin views the user's profile, `SC_Users::user_profile_fields($user)` is triggered via:\n    *   `add_action( 'show_user_profile', array( __CLASS__, 'user_profile_fields' ) );`\n    *   `add_action( 'edit_user_profile', array( __CLASS__, 'user_profile_fields' ) );`\n4.  **Execution:** This function calls `self::load_view( 'admin\u002Fuser\u002Fprofile_fields.php', ... )`, passing the unsanitized metadata. The view (inferred from the patch description) echoes these values directly into the HTML without escaping functions like `esc_attr()`.\n\n## 4. Nonce Acquisition Strategy\nThe attack leverages the standard WordPress profile update mechanism.\n1.  **Tool:** `browser_navigate` to `wp-admin\u002Fprofile.php` using the Contributor's credentials.\n2.  **Extraction:** Use `browser_eval` to extract the core WordPress profile nonce and user ID:\n    *   `nonce = document.querySelector('input[name=\"_wpnonce\"]').value`\n    *   `user_id = document.querySelector('input[name=\"user_id\"]').value`\n3.  **Note:** No plugin-specific AJAX nonce is required for this specific vector.\n\n## 5. Exploitation Strategy\n### Step 1: Injection (Contributor)\nSubmit a POST request to `wp-admin\u002Fprofile.php` to save the XSS payload into the `sc_twitter` field.\n\n*   **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fprofile.php`\n*   **Method:** `POST`\n*   **Content-Type:** `application\u002Fx-www-form-urlencoded`\n*   **Body Parameters:**\n    *   `_wpnonce`: `[EXTRACTED_NONCE]`\n    *   `action`: `update`\n    *   `user_id`: `[CONTRIBUTOR_ID]`\n    *   `sc_twitter`: `\">\u003Cscript>alert(document.domain)\u003C\u002Fscript>`\n    *   `sc_phone`: `555-0199`\n    *   `email`: `contributor@example.com` (required by WP)\n    *   `nickname`: `contributor` (required by WP)\n\n### Step 2: Trigger (Administrator)\nThe Admin views the Contributor's profile to trigger the XSS.\n*   **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fuser-edit.php?user_id=[CONTRIBUTOR_ID]`\n*   **Action:** Simply navigate to this page in the browser using the Admin's session.\n\n## 6. Test Data Setup\n1.  **Plugin:** Install and activate `sprout-clients` version 3.2.2.\n2.  **Users:**\n    *   An Administrator user.\n    *   A Contributor user (to perform the injection).\n\n## 7. Expected Results\n*   Upon the Contributor's POST request, the database should store the literal string `\">\u003Cscript>alert(document.domain)\u003C\u002Fscript>` in the `wp_usermeta` table for the key `sc_twitter`.\n*   Upon the Administrator's navigation to the user edit page, an alert box showing the document domain should appear, confirming the script execution.\n\n## 8. Verification Steps\n1.  **Database Check:** Use WP-CLI to verify the meta is stored unsanitized:\n    *   `wp user meta get [CONTRIBUTOR_ID] sc_twitter`\n2.  **DOM Check:** Use `browser_eval` on the Admin's view of the profile to check for the injected script tag:\n    *   `browser_eval(\"document.body.innerHTML.includes('\u003Cscript>alert(document.domain)\u003C\u002Fscript>')\")`\n\n## 9. Alternative Approaches\nIf the `sc_twitter` field is sanitized by a global filter, try the `sc_note` field or the client creation AJAX endpoint:\n*   **Action:** `sa_create_client` via `wp-admin\u002Fadmin-ajax.php`.\n*   **Parameter:** `sa_client_street` (Note: This may require `publish_posts` capability).\n*   **Nonce Action:** `sc_client_submission` (Localized as `sa_client_nonce` in plugin JS).\n*   **Code Reference:** `SC_Clients_AJAX::maybe_create_client` in `controllers\u002Fclients\u002FClients_AJAX.php`. It uses `self::esc__`, which should be checked for bypasses if the profile fields fail.","The Sprout Clients plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) via several user profile fields and client metadata fields. Authenticated attackers with contributor-level access or above can inject malicious scripts into fields like Twitter, LinkedIn, and Phone, which are then rendered without sanitization or escaping in the WordPress admin interface when a privileged user views the affected profile.","\u002F\u002F controllers\u002Fclients\u002FClients_Users.php:37\npublic static function save_profile_fields( $user_id = 0 ) {\n    if ( ! current_user_can( 'edit_user', $user_id ) ) {\n        return false; }\n\n    update_user_meta( $user_id, self::DOB, $_POST['sc_dob'] );\n    update_user_meta( $user_id, self::PHONE, $_POST['sc_phone'] );\n    update_user_meta( $user_id, self::TWITTER, $_POST['sc_twitter'] );\n    update_user_meta( $user_id, self::LINKEDIN, $_POST['sc_linkedin'] );\n    update_user_meta( $user_id, self::NOTE, $_POST['sc_note'] );\n}\n\n---\n\n\u002F\u002F controllers\u002Fclients\u002FClients_Users.php:21\npublic static function user_profile_fields( $user ) {\n    $user_id = $user->ID;\n    self::load_view( 'admin\u002Fuser\u002Fprofile_fields.php', array(\n        'user' => $user,\n        'phone' => self::get_users_phone( $user_id ),\n        'twitter' => self::get_users_twitter( $user_id ),\n        'linkedin' => self::get_users_linkedin( $user_id ),\n        'dob' => self::get_users_dob( $user_id ),\n        'note' => self::get_users_note( $user_id ),\n        'clients' => Sprout_Client::get_clients_by_user( $user_id ),\n        ) );\n}\n\n---\n\n\u002F\u002F controllers\u002Fclients\u002FClients_Admin_Meta_Boxes.php:364\npublic static function save_meta_box_client_communication( $post_id, $post, $callback_args ) {\n    \u002F\u002F name is filtered via update_post_data\n    $phone = ( isset( $_POST['sa_metabox_phone'] ) && '' !== $_POST['sa_metabox_phone'] ) ? $_POST['sa_metabox_phone'] : '' ;\n    $twitter = ( isset( $_POST['sa_metabox_twitter'] ) && '' !== $_POST['sa_metabox_twitter'] ) ? $_POST['sa_metabox_twitter'] : '' ;\n    $skype = ( isset( $_POST['sa_metabox_skype'] ) && '' !== $_POST['sa_metabox_skype'] ) ? $_POST['sa_metabox_skype'] : '' ;\n    $facebook = ( isset( $_POST['sa_metabox_facebook'] ) && '' !== $_POST['sa_metabox_facebook'] ) ? $_POST['sa_metabox_facebook'] : '' ;\n    $linkedin = ( isset( $_POST['sa_metabox_linkedin'] ) && '' !== $_POST['sa_metabox_linkedin'] ) ? $_POST['sa_metabox_linkedin'] : '' ;","--- \u002Fcontrollers\u002Fclients\u002FClients_Users.php\n+++ \u002Fcontrollers\u002Fclients\u002FClients_Users.php\n@@ -40,11 +40,11 @@\n \t\tif ( ! current_user_can( 'edit_user', $user_id ) ) {\n \t\t\treturn false; }\n \n-\t\tupdate_user_meta( $user_id, self::DOB, $_POST['sc_dob'] );\n-\t\tupdate_user_meta( $user_id, self::PHONE, $_POST['sc_phone'] );\n-\t\tupdate_user_meta( $user_id, self::TWITTER, $_POST['sc_twitter'] );\n-\t\tupdate_user_meta( $user_id, self::LINKEDIN, $_POST['sc_linkedin'] );\n-\t\tupdate_user_meta( $user_id, self::NOTE, $_POST['sc_note'] );\n+\t\tupdate_user_meta( $user_id, self::DOB, sanitize_text_field( wp_unslash( $_POST['sc_dob'] ) ) );\n+\t\tupdate_user_meta( $user_id, self::PHONE, sanitize_text_field( wp_unslash( $_POST['sc_phone'] ) ) );\n+\t\tupdate_user_meta( $user_id, self::TWITTER, sanitize_text_field( wp_unslash( $_POST['sc_twitter'] ) ) );\n+\t\tupdate_user_meta( $user_id, self::LINKEDIN, esc_url_raw( wp_unslash( $_POST['sc_linkedin'] ) ) );\n+\t\tupdate_user_meta( $user_id, self::NOTE, sanitize_textarea_field( wp_unslash( $_POST['sc_note'] ) ) );\n \t}\n--- \u002Fcontrollers\u002Fclients\u002FClients_Admin_Meta_Boxes.php\n+++ \u002Fcontrollers\u002Fclients\u002FClients_Admin_Meta_Boxes.php\n@@ -361,12 +356,12 @@\n \t *\u002F\n \tpublic static function save_meta_box_client_communication( $post_id, $post, $callback_args ) {\n-\t\t$phone = ( isset( $_POST['sa_metabox_phone'] ) && '' !== $_POST['sa_metabox_phone'] ) ? $_POST['sa_metabox_phone'] : '' ;\n-\t\t$twitter = ( isset( $_POST['sa_metabox_twitter'] ) && '' !== $_POST['sa_metabox_twitter'] ) ? $_POST['sa_metabox_twitter'] : '' ;\n-\t\t$skype = ( isset( $_POST['sa_metabox_skype'] ) && '' !== $_POST['sa_metabox_skype'] ) ? $_POST['sa_metabox_skype'] : '' ;\n-\t\t$facebook = ( isset( $_POST['sa_metabox_facebook'] ) && '' !== $_POST['sa_metabox_facebook'] ) ? $_POST['sa_metabox_facebook'] : '' ;\n-\t\t$linkedin = ( isset( $_POST['sa_metabox_linkedin'] ) && '' !== $_POST['sa_metabox_linkedin'] ) ? $_POST['sa_metabox_linkedin'] : '' ;\n+\t\t$phone = ( isset( $_POST['sa_metabox_phone'] ) && '' !== $_POST['sa_metabox_phone'] ) ? sanitize_text_field( wp_unslash( $_POST['sa_metabox_phone'] ) ) : '' ;\n+\t\t$twitter = ( isset( $_POST['sa_metabox_twitter'] ) && '' !== $_POST['sa_metabox_twitter'] ) ? sanitize_text_field( wp_unslash( $_POST['sa_metabox_twitter'] ) ) : '' ;\n+\t\t$skype = ( isset( $_POST['sa_metabox_skype'] ) && '' !== $_POST['sa_metabox_skype'] ) ? sanitize_text_field( wp_unslash( $_POST['sa_metabox_skype'] ) ) : '' ;\n+\t\t$facebook = ( isset( $_POST['sa_metabox_facebook'] ) && '' !== $_POST['sa_metabox_facebook'] ) ? esc_url_raw( wp_unslash( $_POST['sa_metabox_facebook'] ) ) : '' ;\n+\t\t$linkedin = ( isset( $_POST['sa_metabox_linkedin'] ) && '' !== $_POST['sa_metabox_linkedin'] ) ? esc_url_raw( wp_unslash( $_POST['sa_metabox_linkedin'] ) ) : '' ;","The exploit requires an attacker with at least Contributor-level access. \n\n1. The attacker logs into the WordPress dashboard and navigates to their own profile page (`wp-admin\u002Fprofile.php`).\n2. The attacker fills one of the Sprout Client fields (e.g., 'Twitter Handle' or 'Phone') with a payload like: `\">\u003Cscript>alert(document.domain)\u003C\u002Fscript>`.\n3. The attacker submits the profile update. Because the plugin uses raw `$_POST` data in `update_user_meta` without sanitization in `SC_Users::save_profile_fields`, the payload is stored directly in the database.\n4. To trigger the vulnerability, an Administrator navigates to the User Edit screen for the attacker's account (`wp-admin\u002Fuser-edit.php?user_id=[ID]`).\n5. The plugin's `SC_Users::user_profile_fields` function loads the profile view, which echoes the metadata into the HTML without output escaping, resulting in the script executing in the Administrator's browser context.","gemini-3-flash-preview","2026-04-18 22:47:56","2026-04-18 22:49:08",{"type":40,"vulnerable_version":41,"fixed_version":11,"vulnerable_browse":42,"vulnerable_zip":43,"fixed_browse":44,"fixed_zip":45,"all_tags":46},"plugin","3.2.2","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fsprout-clients\u002Ftags\u002F3.2.2","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fsprout-clients.3.2.2.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fsprout-clients\u002Ftags\u002F3.2.3","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fsprout-clients.3.2.3.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fsprout-clients\u002Ftags"]