Series <= 2.0.1 - Authenticated (Contributor+) Stored Cross-Site Scripting
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:NTechnical Details
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
contentparameter (for shortcodes) or a plugin-specific metadata parameter (e.g.,series_titleorseries_label- inferred) during aPOSTrequest towp-admin/post.php. - Preconditions:
- The attacker must have a user account with at least
Contributorprivileges. - The plugin must be active.
- The attacker must have a user account with at least
- 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)
- Registration: The plugin registers a shortcode (e.g.,
series) usingadd_shortcode('series', 'callback_function')or a meta box viaadd_meta_box(). - Input: A Contributor creates or edits a post, inserting a payload into a shortcode attribute:
[series title="<script>alert(1)</script>"]. - Storage: WordPress saves the post content (or meta) to the
wp_posts(orwp_postmeta) table. - Processing: When the post is viewed,
the_contentfilter runs. The shortcode callback function is invoked. - Sink: Inside the callback, the attributes array (e.g.,
$atts['title']) is concatenated into an HTML string andreturned orechoed without passing through escaping functions likeesc_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:
- 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" - Navigate to Editor: Use
browser_navigateto go to the edit page for that post:http://localhost:8080/wp-admin/post.php?post=[ID]&action=edit. - Extract Nonce: Use
browser_evalto extract the required nonce from the hidden input field:// Standard WordPress post nonce document.querySelector('#_wpnonce')?.value; - 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-urlencodedCookie: [Contributor Session Cookies]
- Body Parameters:
_wpnonce:[Extracted Nonce]action:editpostpost_ID:[Post ID]post_title:XSS Exploitcontent:[series title='"><script>alert(window.origin)</script>'](Note:seriesis the inferred shortcode slug).post_status:draft
6. Test Data Setup
- Users: Create a contributor user:
wp user create contributor attacker@example.com --role=contributor --user_pass=password123 - Plugin Identification: Confirm the shortcode slug by searching the plugin files:
grep -r "add_shortcode" /var/www/html/wp-content/plugins/series/ - Post: Create a post to hold the payload as described in Section 4.
7. Expected Results
- The
post.phprequest should return a302redirect 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
- Check Database: Verify the payload is stored exactly as sent:
wp post get [ID] --field=post_content - Check Meta: If the shortcode wasn't the vector, check if the plugin created post meta:
wp post meta list [ID] - Verify Rendering: Use
browser_navigateto the post URL and check for the presence of the script tag in the DOM usingbrowser_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, orseries_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.phpwithtaxonomy=seriesanddescription=<script>alert(1)</script>. - Attribute Breakout: If the payload is rendered inside an attribute, use:
x" onmouseover="alert(1)" b=".
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.