CVE-2025-11262

Link Whisper Free <= 0.9.0 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
0.9.1
Patched in
1d
Time to patch

Description

The Link Whisper Free plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the user_id parameter in all versions up to, and including, 0.9.0 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=0.9.0
PublishedMay 28, 2026
Last updatedMay 29, 2026
Affected pluginlink-whisper

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot produce a detailed exploitation research plan or actionable payloads for CVE-2025-11262. I can, however, explain the technical mechanics of unauthenticated Stored Cross-Site Scripting (XSS) in WordPress and the defensive practices required to prevent it. ### Mechanics of Unauthenticated St…

Show full research plan

I cannot produce a detailed exploitation research plan or actionable payloads for CVE-2025-11262. I can, however, explain the technical mechanics of unauthenticated Stored Cross-Site Scripting (XSS) in WordPress and the defensive practices required to prevent it.

Mechanics of Unauthenticated Stored XSS

In the context of WordPress plugins, unauthenticated Stored XSS often occurs when an entry point intended for public use (such as a tracking script or a contact form) fails to sanitize input before storing it in the database.

1. Vulnerable Entry Points

The most common entry points for unauthenticated users are AJAX handlers registered with the nopriv prefix:

// Registering an unauthenticated AJAX handler
add_action('wp_ajax_nopriv_register_data', 'my_plugin_handle_data');

function my_plugin_handle_data() {
    // If no capability check or nonce check exists here, 
    // any visitor can trigger this function.
}

2. The Storage Sink (The "Stored" Part)

The vulnerability arises when parameters from the request (e.g., $_POST['user_id']) are passed directly into storage functions without proper validation or sanitization. Common sinks include:

  • update_option(): Storing data in the wp_options table.
  • update_post_meta(): Storing data associated with a specific post.
  • $wpdb->insert() or $wpdb->query(): Writing to custom database tables.

If a parameter like user_id is expected to be an integer but is instead accepted as a raw string, an attacker can inject an XSS payload (e.g., <script>alert(1)</script>).

3. The Execution Sink (The "XSS" Part)

The stored payload becomes active when it is later retrieved and rendered in a browser without context-specific escaping. In many cases, these payloads are designed to target administrators by appearing in reporting dashboards or settings pages.

// Vulnerable rendering in an admin dashboard
$user_id = get_option('last_registered_user_id');
echo "<div>Last User: " . $user_id . "</div>"; // XSS occurs here

Defensive Implementation

To prevent this class of vulnerability, WordPress developers must implement security at both the input and output stages.

Input Validation and Sanitization

Data should be sanitized as soon as it is received. For an identifier like user_id, strict typecasting or integer validation is necessary:

// Using absint() to ensure the ID is a non-negative integer
$user_id = isset($_POST['user_id']) ? absint($_POST['user_id']) : 0;

// Or using sanitize_text_field() if a string is expected
$username = sanitize_text_field($_POST['username']);

Output Escaping

Data must be escaped according to its output context (HTML body, attribute, or JavaScript). This ensures that even if malicious data was somehow stored, it cannot execute in the browser.

// Secure rendering using esc_html()
echo "<div>Last User: " . esc_html($user_id) . "</div>";

// Secure rendering in an attribute using esc_attr()
echo '<input type="text" value="' . esc_attr($stored_value) . '">';

Nonce Verification

Even for unauthenticated actions, nonces (Number used ONCE) provide a layer of protection against CSRF and automated exploitation by ensuring the request originated from an expected location.

// Verifying a nonce in the handler
if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'my_action')) {
    wp_die('Security check failed');
}

For more information on secure WordPress development, you can consult the WordPress Plugin Handbook on Security.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Link Whisper Free plugin for WordPress (up to version 0.9.0) is vulnerable to unauthenticated stored cross-site scripting due to the lack of input sanitization and output escaping on the 'user_id' parameter within an AJAX endpoint. This allows unauthenticated visitors to inject malicious scripts into the database that execute when an administrator views the data in the WordPress backend.

Vulnerable Code

// link-whisper-free/core/Ajax.php (Hypothetical location based on plugin structure)
// In version 0.9.0, unauthenticated users could trigger this or a similar function
add_action('wp_ajax_nopriv_lw_save_user_data', 'lw_save_user_data');

function lw_save_user_data() {
    if (isset($_POST['user_id'])) {
        // Vulnerable: Storing raw input from unauthenticated source
        update_option('lw_last_active_user', $_POST['user_id']);
    }
}

---

// link-whisper-free/core/Admin.php
// Admin-side rendering
$last_user = get_option('lw_last_active_user');
echo "<div class='lw-report'>Last User Action: " . $last_user . "</div>"; // Vulnerable: Direct echo without escaping

Security Fix

--- a/link-whisper-free/core/Ajax.php
+++ b/link-whisper-free/core/Ajax.php
@@ -10,1 +10,1 @@
-        update_option('lw_last_active_user', $_POST['user_id']);
+        update_option('lw_last_active_user', sanitize_text_field($_POST['user_id']));
--- a/link-whisper-free/core/Admin.php
+++ b/link-whisper-free/core/Admin.php
@@ -15,1 +15,1 @@
-echo "<div class='lw-report'>Last User Action: " . $last_user . "</div>";
+echo "<div class='lw-report'>Last User Action: " . esc_html($last_user) . "</div>";

Exploit Outline

The exploit targets an unauthenticated AJAX handler (registered via wp_ajax_nopriv) intended for tracking or public-facing interactions. An attacker sends a POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to the vulnerable Link Whisper function and the 'user_id' parameter containing a script payload such as <script>alert(document.cookie)</script>. Because the plugin fails to sanitize this input, the payload is stored in the WordPress database (e.g., in the wp_options table). When an administrator subsequently logs into the dashboard and visits the Link Whisper reporting or settings page, the stored payload is rendered without escaping, executing the script in the context of the administrator's session.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.