Introduction
An unauthenticated stored cross-site scripting vulnerability in the WP Statistics plugin for WordPress lets any external visitor seed a malicious JavaScript payload into the site's analytics database, where it silently waits for an administrator to open a dashboard chart. For the estimated millions of WordPress sites relying on WP Statistics as a privacy-focused analytics alternative to Google Analytics, this means that a single crafted HTTP request from an anonymous attacker can result in script execution inside a privileged admin session.
WP Statistics, maintained by VeronaLabs, is one of the most popular self-hosted analytics plugins in the WordPress ecosystem. It provides site owners with visitor tracking, referral analysis, and traffic reporting without sending data to third-party services. Its broad adoption across WordPress installations makes vulnerabilities in this plugin particularly relevant to the wider web security landscape.
Technical Information
CVE-2026-5231 is classified as CWE-79 (Stored Cross-Site Scripting) and carries a CVSS 3.1 score of 7.2 with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N. The exploit chain spans two distinct components: a server-side referral parser and a client-side chart renderer.
Server-Side Root Cause: Unsanitized Input in ReferralsParser.php
The vulnerability originates in the parse() method of ReferralsParser.php at line 62. When the plugin processes incoming referral data, it checks whether the referral channel definition uses a wildcard domain (*). If a wildcard match occurs, the plugin takes the raw utm_source query parameter value from the incoming request and stores it directly as the channel's display name. In version 14.16.4, this assignment was completely unsanitized:
// v14.16.4 (VULNERABLE) if ($channelDomain == '*') { $channels[$key]['name'] = $value; }
This means any string provided in the utm_source parameter, including HTML and JavaScript, is written verbatim to the database as the source_name.
Client-Side Root Cause: innerHTML Injection in chart.js
The second half of the chain lives in chart.js, which builds the admin dashboard charts. Both the externalTooltipHandler (tooltip rendering) and updateLegend (legend rendering) functions construct HTML using template literals and inject them into the DOM via innerHTML. In version 14.16.4, dataset.label, which carries the attacker-controlled source_name, was interpolated directly without any escaping:
// v14.16.4 (VULNERABLE) — tooltip handler innerHtml += `... ${dataset.label} ...`;
// v14.16.4 (VULNERABLE) — legend builder legendItem.innerHTML = `${dataset.label} ...`;
Attack Flow
The exploitation path is as follows:
-
An unauthenticated attacker sends one or more HTTP requests to any public-facing page on the target WordPress site. These requests include a crafted
utm_sourceparameter containing a JavaScript payload (for example, an<img>tag with anonerrorhandler or a<script>block). -
The WP Statistics referral parser processes the request. Because the channel definition uses a wildcard domain, the raw
utm_sourcevalue is stored in the database as thesource_namewithout sanitization. -
The malicious payload sits dormant in the database.
-
When a WordPress administrator navigates to the Referrals Overview or Social Media analytics pages in the admin dashboard, the chart renderer reads the stored
source_nameand inserts it into the chart legend or tooltip markup viainnerHTML. -
The browser parses the injected HTML, executing the attacker's JavaScript in the context of the administrator's authenticated session.
The CVSS scope change indicator (S:C) is significant here: the payload is seeded in the unauthenticated, public-facing context but executes in the privileged admin context. While the CVSS vector marks Confidentiality and Integrity impacts as Low, the practical consequence of arbitrary JavaScript execution in an admin session can include creating new admin accounts, modifying site content, or installing backdoor plugins.
The attack requires no privileges (PR:N), no user interaction for the initial seeding (UI:N), and has low complexity (AC:L), making it trivially automatable at scale.
Patch Information
The vulnerability was remediated in WP Statistics version 14.16.5, released on April 11, 2026. The patch applies a defense-in-depth strategy, addressing the issue at both the server-side input layer and the client-side output layer. The changeset is tracked between WordPress plugin SVN revisions 3483860 (v14.16.4) and 3503795 (v14.16.5).
Server-Side Fix: Input Sanitization in ReferralsParser.php
In version 14.16.5, the raw utm_source value is now wrapped with WordPress's built-in sanitize_text_field() function before being stored:
// v14.16.5 (PATCHED) if ($channelDomain == '*') { $channels[$key]['name'] = sanitize_text_field($value); }
sanitize_text_field() strips all HTML tags, removes extra whitespace and line breaks, and encodes special characters. A malicious utm_source value such as <img src=x onerror=alert(1)> would be neutralized to a harmless plain-text string before it ever reaches the database.
Client-Side Fix: Output Escaping in chart.js
In version 14.16.5, both the externalTooltipHandler and updateLegend functions now pass dataset.label through a new escape function before insertion:
// v14.16.5 (PATCHED) — tooltip handler innerHtml += `... ${wps_js.escapeHtml(dataset.label)} ...`;
// v14.16.5 (PATCHED) — legend builder legendItem.innerHTML = `${wps_js.escapeHtml(dataset.label)} ...`;
New Helper: wps_js.escapeHtml() in helper.js
The wps_js.escapeHtml() function did not exist in version 14.16.4 and was introduced as part of this patch in helper.js. It performs character-entity replacement for the five most critical HTML special characters (&, <, >, ", '), which is sufficient to prevent HTML injection in an innerHTML context.
Taken together, the patch ensures that (1) the malicious payload is stripped before storage via sanitize_text_field() on the server, and (2) even if a tainted value existed in the database from records written before the patch was applied, it would still be safely escaped via wps_js.escapeHtml() before being rendered in the admin-facing chart UI.
Affected Systems and Versions
All versions of the WP Statistics plugin for WordPress up to and including 14.16.4 are affected. The vulnerability is present in any WordPress installation running a vulnerable version of the plugin where referral tracking is active (which is the default configuration).
The fix is available in version 14.16.5 and later.
References
- NVD Entry for CVE-2026-5231
- Wordfence Vulnerability Advisory
- WP Statistics Plugin Developer Changelog
- ReferralsParser.php (v14.16.4, vulnerable)
- ReferralsParser.php (v14.16.5, patched)
- chart.js (v14.16.4, vulnerable)
- chart.js (v14.16.5, patched)
- helper.js (v14.16.5, new escapeHtml function)
- helper.js (v14.16.4, no escapeHtml function)
- WordPress Trac: ReferralsParser.php in tags/14.16.4
- WordPress Trac: chart.js in tags/14.16.4
- WordPress Trac: Patch Changeset (3483860 to 3503795)
- CISA Known Exploited Vulnerabilities Catalog



