PrivateContent Free <= 1.2.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'align' Shortcode Attribute
Description
The PrivateContent Free plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'align' shortcode attribute in the [pc-login-form] shortcode in all versions up to, and including, 1.2.0. This is due to insufficient input sanitization and output escaping on the 'align' attribute. Specifically, the attribute value flows from the shortcode through pc_login_form() to pc_static::form_align(), where it is directly concatenated into an HTML class attribute without esc_attr() or any 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
<=1.2.0What Changed in the Fix
Changes introduced in v1.3.0
Source Code
WordPress.org SVN# Research Plan: CVE-2026-4025 - PrivateContent Free Stored XSS ## 1. Vulnerability Summary The **PrivateContent Free** plugin (up to 1.2.0) contains a stored cross-site scripting (XSS) vulnerability. The plugin fails to sanitize or escape the `align` attribute within the `[pc-login-form]` shortcod…
Show full research plan
Research Plan: CVE-2026-4025 - PrivateContent Free Stored XSS
1. Vulnerability Summary
The PrivateContent Free plugin (up to 1.2.0) contains a stored cross-site scripting (XSS) vulnerability. The plugin fails to sanitize or escape the align attribute within the [pc-login-form] shortcode (and likely other related shortcodes). This value is passed to pc_static::form_align() and concatenated directly into an HTML class attribute without using esc_attr(). Authenticated users with Contributor level access or higher can inject malicious JavaScript into pages, which will execute when any user (including administrators) views that page.
2. Attack Vector Analysis
- Endpoint/Shortcode:
[pc-login-form](also check[pc-registration-form]and[pc-user-del-box]). - Vulnerable Parameter: The
alignattribute within the shortcode. - Authentication Level: Contributor+ (any role allowed to create or edit posts and use shortcodes).
- Preconditions: The plugin must be active, and a post containing the malicious shortcode must be published or previewed.
3. Code Flow
Based on the vulnerability description and provided source files:
- Entry Point: A user with Contributor permissions creates a post with the shortcode
[pc-login-form align='PAYLOAD']. - Shortcode Handling: WordPress parses the shortcode and calls the handler function (inferred as
pc_login_form()or therender_callbackdefined in Gutenberg integration likepvtcont_login_guten_handler). - Data Processing: The
alignattribute value is extracted from the$attsarray. - Vulnerable Sink: The value is passed to
pc_static::form_align(). - Output Generation: Inside
pc_static::form_align(), the code likely performs a concatenation similar to:
This returned string is then placed into a template:// Inferred vulnerable code in pc_static::form_align() return 'pc_form_' . $align_attr;// Inferred output context echo '<div class="' . pc_static::form_align($atts['align']) . '">'; - Result: The lack of
esc_attr()allows an attacker to break out of theclassattribute and inject event handlers (e.g.,onmouseover,autofocus onfocus).
4. Nonce Acquisition Strategy
Shortcodes in WordPress do not require nonces for rendering. They are processed on the server whenever a post is rendered for display. Therefore, no nonce is required for the core exploitation of this vulnerability.
However, if the PoC involves using the Gutenberg editor via the REST API to save the post, a standard WordPress REST API nonce (_wpnonce) would be required. This can be obtained by:
- Navigating to the post editor page (
/wp-admin/post-new.php). - Extracting the
wpApiSettings.noncefrom the page source usingbrowser_eval.
5. Exploitation Strategy
The goal is to demonstrate that a Contributor user can inject a script that executes when viewed.
Step 1: Create a Post with Payload
Using the http_request tool, the agent will authenticate as a Contributor and create a post containing the malicious shortcode.
- URL:
http://localhost:8080/wp-admin/post-new.php(to initiate) or usingwp-clito create the post directly. - Shortcode Payload:
[pc-login-form align=' " onmouseover="alert(document.domain)" style="padding:100px;border:1px solid red;" ']- Note: We use
styleto make the element large and easy to trigger theonmouseoverevent.
- Note: We use
Step 2: View the Post
The agent will navigate to the newly created post's permalink using http_request.
Step 3: Analyze Response
Search the response body for the injected payload to confirm it rendered without escaping.
- Target String:
class="pc_login_form " onmouseover="alert(document.domain)"
6. Test Data Setup
- Plugin: Ensure
privatecontent-freeversion 1.2.0 is installed and active. - User: Create a user with the Contributor role.
wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Post Content: Use the following content for the post:
Check out this login form: [pc-login-form align=' " onmouseover="alert(document.domain)" ']
7. Expected Results
- The HTTP response from the published page should contain the raw, unescaped payload within the class attribute of a
divorformelement. - Specifically, the HTML output should look like:
<div class="pc_login_form " onmouseover="alert(document.domain)" ... - The
"character in thealignattribute will close theclassattribute value, allowing theonmouseoverattribute to be injected as a separate HTML attribute.
8. Verification Steps
- HTML Inspection: After the
http_requestto the post, usegrepor string matching on the output:# Confirm the injection grep -P 'class="[^"]*"\s+onmouseover="alert' - Database Check: Verify the shortcode was stored correctly via
wp-cli:wp post get <POST_ID> --field=post_content
9. Alternative Approaches
If [pc-login-form] is patched or blocked, try the other Gutenberg-integrated elements found in the source:
- Registration Form:
[pc-registration-form align=' " onmouseover="alert(1)" '](linked tobuilders_integration/guten_elements/registr/registr.php) - User Deletion Box:
[pc-user-del-box align=' " onmouseover="alert(1)" '](linked tobuilders_integration/guten_elements/user_del/user_del.php)
Both of these use the pc_align / align attribute pattern and are registered with similar render handlers that likely share the vulnerable pc_static::form_align() function.
Summary
The PrivateContent Free plugin for WordPress (up to version 1.2.0) is vulnerable to Stored Cross-Site Scripting due to insufficient input sanitization and output escaping on the 'align' attribute in shortcodes such as [pc-login-form]. An authenticated attacker with Contributor-level access or higher can inject arbitrary JavaScript by breaking out of the HTML class attribute context where the value is concatenated in pc_static::form_align().
Vulnerable Code
// builders_integration/guten_elements/login/login.php @ 1.2.0 'pc_align' => array( 'label' => esc_html__('Form alignment', 'privatecontent-free'), 'type' => 'select', 'opts' => array( 'center' => esc_html__('Center', 'privatecontent-free'), 'left' => esc_html__('Left', 'privatecontent-free'), 'right' => esc_html__('Right', 'privatecontent-free'), ), 'default' => 'center', 'panel' => 'main', ), --- // builders_integration/guten_elements/registr/registr.php @ 1.2.0 'pc_align' => array( 'label' => esc_html__('Form alignment', 'privatecontent-free'), 'type' => 'select', 'opts' => array( 'center' => esc_html__('Center', 'privatecontent-free'), 'left' => esc_html__('Left', 'privatecontent-free'), 'right' => esc_html__('Right', 'privatecontent-free'), ), 'default' => 'center', 'panel' => 'main', ),
Security Fix
@@ -13,12 +13,12 @@ 'panel' => 'main', ), 'pc_align' => array( - 'label' => esc_html__('Form alignment', 'privatecontent-free'), + 'label' => esc_html__('Form alignment', 'pc_ml'), 'type' => 'select', 'opts' => array( - 'center' => esc_html__('Center', 'privatecontent-free'), - 'left' => esc_html__('Left', 'privatecontent-free'), - 'right' => esc_html__('Right', 'privatecontent-free'), + 'center' => esc_html__('Center', 'pc_ml'), + 'left' => esc_html__('Left', 'pc_ml'), + 'right' => esc_html__('Right', 'pc_ml'), ), 'default' => 'center', 'panel' => 'main', (truncated)
Exploit Outline
1. Log in to the WordPress site as a user with Contributor-level permissions (or higher). 2. Create a new post or edit an existing one. 3. Insert a shortcode for a PrivateContent form (e.g., [pc-login-form]) and provide a malicious payload for the 'align' attribute, such as: [pc-login-form align=' " onmouseover="alert(document.domain)" ']. 4. Publish the post. 5. View the post as any user (including an Administrator). The double-quote in the payload will close the intended class attribute, allowing the onmouseover event handler to be injected as a new attribute on the form container element. The script executes when a user hovers over the form.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.