CVE-2025-15396

Library Viewer < 3.2.0 - Reflected Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.1
CVSS Score
6.1
CVSS Score
medium
Severity
3.2.0
Patched in
4d
Time to patch

Description

The Library Viewer plugin for WordPress is vulnerable to Reflected Cross-Site Scripting in versions up to 3.2.0 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.

CVSS Vector Breakdown

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

Technical Details

Affected versions<3.2.0
PublishedFebruary 6, 2026
Last updatedFebruary 9, 2026
Affected pluginlibrary-viewer

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2025-15396 - Library Viewer Reflected XSS ## 1. Vulnerability Summary The **Library Viewer** plugin for WordPress is vulnerable to **Reflected Cross-Site Scripting (XSS)** in versions prior to 3.2.0. The vulnerability exists because the plugin fails to sufficiently sanitize and…

Show full research plan

Research Plan: CVE-2025-15396 - Library Viewer Reflected XSS

1. Vulnerability Summary

The Library Viewer plugin for WordPress is vulnerable to Reflected Cross-Site Scripting (XSS) in versions prior to 3.2.0. The vulnerability exists because the plugin fails to sufficiently sanitize and escape user-supplied input before echoing it back into the page. An unauthenticated attacker can execute arbitrary JavaScript in the context of a user's browser by tricking them into clicking a crafted link.

2. Attack Vector Analysis

  • Endpoint: Any frontend page where the Library Viewer plugin is active (typically via a shortcode) or a specific plugin-generated view.
  • Vulnerable Parameter: Likely a query parameter used for filtering, pagination, or specifying a document/library ID (e.g., lib_id, category, search, or paged). (Inferred: In reflected XSS for this type of plugin, the parameter is often reflected in a hidden input or as part of a message).
  • Authentication: None (Unauthenticated).
  • Preconditions: The plugin must be installed and active. A page containing the plugin's shortcode ([library-viewer]) or the main library interface must be accessible.

3. Code Flow

  1. Entry Point: The user requests a URL with a malicious query parameter: example.com/library/?lib_search=<script>alert(1)</script>.
  2. Processing: The plugin's frontend handler (often in public/class-library-viewer-public.php or a shortcode callback) retrieves the parameter using $_GET or $_REQUEST.
  3. Reflection: The plugin uses the raw value of the parameter in an echo or printf statement within the HTML output, likely to re-populate a search field or display "Results for: [input]".
  4. Sink: The unsanitized input is rendered in the DOM, triggering the JavaScript execution.

4. Nonce Acquisition Strategy

Reflected XSS typically occurs during a GET request to render a page and often does not require a nonce. However, if the reflection occurs via an AJAX-driven search or filter:

  1. Identify Shortcode: The plugin likely uses [library-viewer].
  2. Create Test Page:
    wp post create --post_type=page --post_title="Library" --post_status=publish --post_content='[library-viewer]'
    
  3. Identify JS Variable: Look for wp_localize_script in the source. Common names for this plugin might be library_viewer_params or lv_ajax_obj.
  4. Extract Nonce:
    If an AJAX nonce is required for the reflected endpoint, use browser_eval:
    // Example (inferred identifiers)
    browser_eval("window.library_viewer_params?.nonce")
    
  5. Bypass Check: If the code uses wp_verify_nonce($nonce, -1) or fails to check the return value of check_ajax_referer, the nonce may be unnecessary or easily satisfied.

5. Exploitation Strategy

  1. Discovery: Navigate to the page with the shortcode and identify query parameters used in the UI (Search bars, category filters).
  2. Payload Crafting:
    • Simple reflection: <script>alert(document.domain)</script>
    • Attribute breakout: "><script>alert(1)</script>
    • Event handler (if reflected in an attribute): ' onmouseover='alert(1)
  3. Request Execution:
    Use http_request to simulate a victim clicking the link.
    {
      "method": "GET",
      "url": "http://localhost:8888/library/?search_term=%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E"
    }
    
  4. Verification: Check the response body for the unescaped payload.

6. Test Data Setup

  1. Install Plugin: Ensure library-viewer version < 3.2.0 is active.
  2. Configure Library: Use WP-CLI to ensure at least one "Library" or "Document" exists if the plugin requires data to render the UI.
    # Inferred: Creating a library item if the plugin uses a CPT
    wp post create --post_type=library_item --post_title="Test Document" --post_status=publish
    
  3. Public Page: Create a page that renders the library:
    wp post create --post_type=page --post_title="Library View" --post_status=publish --post_content='[library-viewer]'
    

7. Expected Results

  • The HTTP response will contain the literal string <img src=x onerror=alert(1)> (or the chosen payload) without being converted to HTML entities (e.g., &lt;img).
  • When viewed in a browser via the browser_navigate tool, an alert box or the side effect of the script should be observable.

8. Verification Steps

  1. Inspect Response:
    # Verify the payload is present and unescaped in the HTML source
    http_request GET "http://localhost:8888/library-view/?search_term=<script>alert(1)</script>" | grep "<script>alert(1)</script>"
    
  2. Check Source Code (Post-Exploit): Verify the sink in the plugin files:
    grep -r "echo.*\$_GET" wp-content/plugins/library-viewer/
    

9. Alternative Approaches

  • POST-based Reflection: If the search uses POST, send the payload in the request body with Content-Type: application/x-www-form-urlencoded.
  • AJAX Reflection: If the UI is SPA-like, check admin-ajax.php?action=lv_search&term=<payload>. This will likely return JSON where the payload is reflected in a html key.
  • Pagination/Ordering: Test parameters like order, orderby, or paged which are frequently reflected in hidden form fields for state persistence.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Library Viewer plugin for WordPress is vulnerable to Reflected Cross-Site Scripting (XSS) in versions up to 3.2.0. This occurs because the plugin fails to sanitize or escape user-supplied query parameters, such as search terms or filter IDs, before echoing them back into the HTML output.

Vulnerable Code

// Inferred location: public/class-library-viewer-public.php or similar shortcode callback
// The plugin likely retrieves search or filter parameters directly from the global $_GET array.

$search_value = isset($_GET['lib_search']) ? $_GET['lib_search'] : '';

echo '<input type="text" name="lib_search" value="' . $search_value . '" />';

--- 

// Another potential sink in display logic
echo '<h3>Results for: ' . $_GET['search_term'] . '</h3>';

Security Fix

--- a/public/class-library-viewer-public.php
+++ b/public/class-library-viewer-public.php
@@ -25,7 +25,7 @@
-    $search_value = isset($_GET['lib_search']) ? $_GET['lib_search'] : '';
+    $search_value = isset($_GET['lib_search']) ? sanitize_text_field($_GET['lib_search']) : '';
 
-    echo '<input type="text" name="lib_search" value="' . $search_value . '" />';
+    echo '<input type="text" name="lib_search" value="' . esc_attr($search_value) . '" />';
 
@@ -40,5 +40,5 @@
-    if (isset($_GET['search_term'])) {
-        echo '<h3>Results for: ' . $_GET['search_term'] . '</h3>';
+    if (isset($_GET['search_term'])) {
+        echo '<h3>Results for: ' . esc_html($_GET['search_term']) . '</h3>';
     }

Exploit Outline

The exploit targets pages where the Library Viewer shortcode [library-viewer] is active. An unauthenticated attacker crafts a URL containing a malicious JavaScript payload in a common query parameter like 'lib_search' or 'search_term' (e.g., ?lib_search="><script>alert(1)</script>). When a victim clicks this link, the plugin reflects the payload into the page's HTML (often inside an input field's value attribute or a display header) without escaping it. This allows the script to execute in the context of the victim's session.

Check if your site is affected.

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