Private Comment <= 0.0.4 - Authenticated (Administrator+) Stored Cross-Site Scripting via Label Text Setting
Description
The Private Comment plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'Label text' setting in all versions up to, and including, 0.0.4. This is due to insufficient input sanitization and output escaping on the plugin's label text option. This makes it possible for authenticated attackers, with Administrator-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=0.0.4Source Code
WordPress.org SVNThis research plan outlines the technical steps to exploit **CVE-2026-2281**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Private Comment** WordPress plugin (versions <= 0.0.4). ### 1. Vulnerability Summary The "Private Comment" plugin allows administrators to configure a "Label text…
Show full research plan
This research plan outlines the technical steps to exploit CVE-2026-2281, a Stored Cross-Site Scripting (XSS) vulnerability in the Private Comment WordPress plugin (versions <= 0.0.4).
1. Vulnerability Summary
The "Private Comment" plugin allows administrators to configure a "Label text" for private comments. The vulnerability exists because the plugin fails to sanitize the input when saving this setting and fails to escape the output when rendering it on the frontend (e.g., in the comment form). While administrators usually have the unfiltered_html capability, this vulnerability is critical in Multi-site environments or installations where DISALLOW_UNFILTERED_HTML is defined, as it allows an administrator to bypass those restrictions and execute arbitrary JavaScript in the context of other users, including Super Admins.
2. Attack Vector Analysis
- Vulnerable Setting: Label text (inferred option name:
private_comment_label). - Endpoint:
/wp-admin/options.php(Standard WordPress Settings API) or a custom admin page. - Payload Parameter:
private_comment_label(inferred). - Authentication: Requires Administrator-level privileges.
- Precondition: The vulnerability is most relevant when
unfiltered_htmlis disabled for administrators (common in Multi-site or hardened installs).
3. Code Flow (Inferred)
- Registration: The plugin uses
register_setting()in a function hooked toadmin_init. It likely does not provide asanitize_callbackfor the label text option. - Storage: When the admin saves settings, WordPress calls
update_option()with the raw POST data. - Retrieval: On the frontend (comment form), the plugin calls
get_option('private_comment_label'). - Rendering: The retrieved value is echoed directly into the HTML (likely near the comment textarea or as a checkbox label) without using
esc_html()oresc_attr().
4. Nonce Acquisition Strategy
Since this vulnerability involves an administrator modifying plugin settings, the exploit must navigate the WordPress Admin UI to handle standard CSRF protections (nonces).
- Identify Settings Page: Navigate to the plugin settings page (usually under
Settings->Private Comment). - Extract Nonces:
- The standard WordPress Settings API uses a hidden field named
_wpnonce. - The action for the settings group must be identified.
- The standard WordPress Settings API uses a hidden field named
- Browser-based extraction:
// Use browser_eval to find the nonce in the settings form const nonce = document.querySelector('input[name="_wpnonce"]')?.value; const option_page = document.querySelector('input[name="option_page"]')?.value; return { nonce, option_page };
5. Exploitation Strategy
The goal is to inject a script that triggers when anyone views a post with comments enabled.
Step 1: Environment Hardening (Pre-exploit)
To demonstrate the vulnerability as a security flaw (rather than a feature), the unfiltered_html capability must be disabled for the administrator.
Step 2: Inject Payload
Submit a POST request to /wp-admin/options.php with the malicious payload.
- URL:
https://target.example.com/wp-admin/options.php - Method: POST
- Content-Type:
application/x-www-form-urlencoded - Parameters:
option_page:private_comment_settings_group(inferred)action:update_wpnonce: [EXTRACTED_NONCE]private_comment_label:<script>alert(document.domain)</script>(inferred parameter name)
Step 3: Trigger Execution
Navigate to any public post that has comments enabled. The "Private Comment" checkbox and its malicious label should render, executing the script.
6. Test Data Setup
- Install Plugin:
private-commentversion 0.0.4. - Restrict Capabilities: Add
define( 'DISALLOW_UNFILTERED_HTML', true );towp-config.php. - Create Admin User: Ensure an administrator account exists.
- Create Content: Ensure at least one public post exists where comments are allowed.
- Enable Plugin Feature: Ensure the "Private Comment" feature is active so the label renders on the frontend.
7. Expected Results
- The POST request to
options.phpshould return a302 Foundredirecting back to the settings page withsettings-updated=true. - When visiting a frontend post, an alert box showing the document domain should appear.
- The HTML source of the comment form should contain the raw
<script>tag.
8. Verification Steps
- Verify Database State:
Confirm it contains thewp option get private_comment_label --allow-root<script>tag. - Verify Frontend Output:
Usehttp_requestto GET a post page and check if the payload is present without escaping:# Look for the unescaped script tag in the response body grep "<script>alert(document.domain)</script>" response.html
9. Alternative Approaches
If the plugin does not use the standard Settings API (options.php):
- Custom AJAX: Search for
wp_ajax_hooks in the plugin code. If found, identify the action (e.g.,pc_save_settings) and the nonce (likely localized viawp_localize_script). - Direct POST: Check if the plugin processes settings via
admin_initdirectly by checkingif (isset($_POST['...'])). - Payload Variation: If
<script>is blocked by a WAF, try attribute-based XSS:"><img src=x onerror=alert(1)>" onmouseover="alert(1)(if injected into an attribute context)
Note on Inferred Identifiers:
The identifiers private_comment_label and private_comment_settings_group are based on standard WordPress plugin naming conventions. The agent must verify these by inspecting the HTML of the settings page using browser_navigate and browser_eval before sending the POST request.
Summary
The Private Comment plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'Label text' setting in versions up to 0.0.4. This occurs due to a lack of sanitization on the saved option and a lack of escaping when the label is rendered in the frontend comment form, allowing administrators to bypass unfiltered_html restrictions.
Vulnerable Code
// In the plugin settings registration (likely in an admin_init hook) register_setting('private_comment_options', 'private_comment_label'); --- // In the frontend rendering logic (likely near the comment form) $label_text = get_option('private_comment_label', 'Private Comment'); echo '<label for="private_comment">' . $label_text . '</label>';
Security Fix
@@ -15,7 +15,7 @@ function private_comment_register_settings() { - register_setting('private_comment_options', 'private_comment_label'); + register_setting('private_comment_options', 'private_comment_label', 'sanitize_text_field'); } @@ -45,5 +45,5 @@ $label_text = get_option('private_comment_label', 'Private Comment'); -echo '<label for="private_comment">' . $label_text . '</label>'; +echo '<label for="private_comment">' . esc_html($label_text) . '</label>';
Exploit Outline
The exploit requires Administrator privileges and is most effective in environments where 'unfiltered_html' is disabled (e.g., WordPress Multisite). 1. The attacker logs into the WordPress dashboard and navigates to the Private Comment settings page. 2. The attacker identifies the input field for the 'Label text' (often associated with the option name 'private_comment_label'). 3. A payload such as '<script>alert(document.domain)</script>' is entered into the field and the settings are saved. 4. Because the plugin uses the standard options.php endpoint and fails to sanitize this input, the script is stored in the database. 5. When any user visits a post on the frontend that allows comments, the plugin retrieves the malicious label and echoes it directly into the HTML source, triggering the execution of the script.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.