CVE-2025-15483

Link Hopper <= 2.5 - Authenticated (Administrator+) Stored Cross-Site Scripting via 'hop_name' Parameter

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
4.4
CVSS Score
4.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Link Hopper plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the ‘hop_name’ parameter in all versions up to, and including, 2.5 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level access, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.5
PublishedFebruary 13, 2026
Last updatedApril 15, 2026
Affected pluginlink-hopper
Research Plan
Unverified

# Research Plan: CVE-2025-15483 - Link Hopper Stored XSS ## 1. Vulnerability Summary The **Link Hopper** plugin for WordPress (versions <= 2.5) is vulnerable to **Stored Cross-Site Scripting (XSS)** via the `hop_name` parameter. The vulnerability exists because the plugin fails to sanitize the link…

Show full research plan

Research Plan: CVE-2025-15483 - Link Hopper Stored XSS

1. Vulnerability Summary

The Link Hopper plugin for WordPress (versions <= 2.5) is vulnerable to Stored Cross-Site Scripting (XSS) via the hop_name parameter. The vulnerability exists because the plugin fails to sanitize the link name when it is saved to the database and fails to escape the value when it is rendered in the admin dashboard. While the attack requires Administrator-level privileges, it is significant in environments where unfiltered_html is disabled (such as WordPress Multisite or hardened installations), as it allows an administrator to execute arbitrary JavaScript in the context of other administrators or super-admins.

2. Attack Vector Analysis

  • Endpoint: Likely a link creation/editing form in the WordPress admin area or an AJAX handler.
  • Vulnerable Parameter: hop_name (inferred).
  • Authentication: Authenticated, Administrator+.
  • Impact: Stored XSS. Execution occurs when an administrator views the list of "hops" (links) created within the plugin.
  • Precondition: unfiltered_html must be disabled for the user (e.g., standard Administrator on a Multisite installation).

3. Code Flow (Inferred)

  1. Input: An administrator navigates to the Link Hopper management page (e.g., wp-admin/admin.php?page=link-hopper-add or similar).
  2. Processing: The administrator submits a form to create or update a "hop". The request is handled by a function hooked to admin_init or a wp_ajax_ action.
  3. Storage: The plugin retrieves $_POST['hop_name']. It likely saves this value using update_post_meta() or a direct $wpdb->insert()/$wpdb->update() call without applying sanitize_text_field().
  4. Retrieval: When an administrator visits the link overview page (wp-admin/admin.php?page=link-hopper), the plugin retrieves the stored hop_name.
  5. Sink: The retrieved hop_name is echoed directly into the HTML table or list without using esc_html() or esc_attr().

4. Nonce Acquisition Strategy

Since the vulnerability is in the admin area, the form used to save the link will almost certainly be protected by a WordPress nonce.

  1. Identify the Page: Search the plugin code for add_menu_page or add_submenu_page to find the admin menu slug.
    • grep -r "add_menu_page" wp-content/plugins/link-hopper/
  2. Navigate to Form: Use browser_navigate to go to the link creation page.
  3. Extract Nonce:
    • If it is a standard form, the nonce is likely in a hidden input field: browser_eval("document.querySelector('input[name=\"_wpnonce\"]')?.value").
    • If it is an AJAX-driven interface, check for localized scripts: browser_eval("window.link_hopper_params?.nonce") (inferred variable name).
  4. Identify Action: Search for the form handler to determine if the nonce is checked using check_admin_referer() or check_ajax_referer().

5. Exploitation Strategy

Step 1: Force unfiltered_html Restriction

To simulate the vulnerable condition on a standard WordPress install, disable unfiltered_html.

  • Command: wp config set DISALLOW_UNFILTERED_HTML true --raw

Step 2: Identify the Save Action

Determine how the plugin saves links. Look for $_POST['hop_name'] in the codebase.

  • grep -r "hop_name" wp-content/plugins/link-hopper/

Step 3: Trigger the Storage

Send an authenticated POST request to save a new hop with the XSS payload.

  • URL: https://[target]/wp-admin/admin.php?page=link-hopper-settings (inferred) or admin-ajax.php.
  • Method: POST
  • Body (URL-encoded):
    action=lh_save_hop&  (inferred action)
    hop_name=<script>alert(document.domain)</script>&
    hop_url=https://example.com&
    _wpnonce=[EXTRACTED_NONCE]
    

Step 4: Trigger the Execution

Navigate to the page where links are listed.

  • URL: https://[target]/wp-admin/admin.php?page=link-hopper (inferred)

6. Test Data Setup

  1. Plugin Activation: Ensure link-hopper is installed and active.
  2. User Creation: Create an administrator user.
    • wp user create admin_tester admin@example.com --role=administrator --user_pass=password123
  3. Config Change: Disable unfiltered_html (as noted in Step 1).
  4. Identify Slug: Find the exact admin slug for the Link Hopper list page.

7. Expected Results

  • The POST request should return a 302 redirect or a success message.
  • Upon navigating to the link list page, a JavaScript alert box displaying the document domain should appear.
  • Viewing the page source should show the raw <script> tag within the HTML table row for the hop_name column.

8. Verification Steps

  1. Database Check: Verify the payload is stored raw in the database.
    • wp db query "SELECT * FROM wp_posts WHERE post_title LIKE '%<script>%'" (if stored as a CPT)
    • OR wp db query "SELECT * FROM wp_options WHERE option_name LIKE '%link_hopper%'"
  2. HTML Inspection: Verify the absence of escaping in the response.
    • Use http_request to fetch the admin list page and check if <script> is present and NOT encoded as &lt;script&gt;.

9. Alternative Approaches

  • Post Meta Exploitation: If hop_name is stored in wp_postmeta, try injecting via post.php if the plugin uses a custom meta box.
  • Attribute Injection: If the hop_name is echoed inside an attribute (e.g., <input value="[hop_name]">), use a payload like " onmouseover="alert(1)".
  • Multisite Test: If the single-site config change doesn't yield results, convert the site to a multisite network and test as a site-administrator (who lacks unfiltered_html by default).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Link Hopper plugin for WordPress (versions <= 2.5) is vulnerable to Stored Cross-Site Scripting (XSS) via the 'hop_name' parameter. This allows authenticated administrators to inject arbitrary JavaScript into link names, which then executes in the browser of any user (typically other administrators) viewing the link management dashboard, especially in environments where 'unfiltered_html' is restricted.

Vulnerable Code

// link-hopper/includes/admin-save.php (approximate location)
// Processing the link creation/update form
if ( isset( $_POST['hop_name'] ) ) {
    $hop_name = $_POST['hop_name']; // Vulnerable: Input is not sanitized before being stored in the database
    // ... database storage logic ...
}

---

// link-hopper/includes/admin-view.php (approximate location)
// Rendering the list of links in the WP-Admin dashboard
foreach ( $hops as $hop ) {
    echo '<tr>';
    echo '<td>' . $hop->hop_name . '</td>'; // Vulnerable: Output is not escaped with esc_html() or esc_attr()
    echo '</tr>';
}

Security Fix

--- a/link-hopper/includes/admin-save.php
+++ b/link-hopper/includes/admin-save.php
@@ -10,7 +10,7 @@
 function lh_save_hop_handler() {
     if ( isset( $_POST['hop_name'] ) ) {
-        $hop_name = $_POST['hop_name'];
+        $hop_name = sanitize_text_field( $_POST['hop_name'] );
         // ... database update ...
     }
 }
--- a/link-hopper/includes/admin-view.php
+++ b/link-hopper/includes/admin-view.php
@@ -50,7 +50,7 @@
 foreach ( $hops as $hop ) {
     echo '<tr>';
-    echo '<td>' . $hop->hop_name . '</td>';
+    echo '<td>' . esc_html( $hop->hop_name ) . '</td>';
     echo '</tr>';
 }

Exploit Outline

To exploit this vulnerability, an attacker requires Administrator-level access on a WordPress site where the `unfiltered_html` capability is disabled (common in Multisite environments). 1. Log in to the WordPress dashboard as an Administrator. 2. Navigate to the Link Hopper management page (e.g., 'Add New Hop' or 'Edit Hop'). 3. In the 'hop_name' field, input a payload such as: <script>alert(document.cookie)</script>. 4. Submit the form to save the link. This stores the malicious script in the database without sanitization. 5. Trigger the execution by navigating to the plugin's overview page where all created links are listed. The script will execute in the context of any user viewing this list.

Check if your site is affected.

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