Brief Summary: CVE-2026-5231 — Unauthenticated Stored XSS in WP Statistics via utm_source Parameter

A short review of CVE-2026-5231, a high severity stored cross-site scripting vulnerability in the WP Statistics WordPress plugin that allows unauthenticated attackers to inject scripts via the utm_source parameter. Includes patch details for version 14.16.5.

CVE Analysis

7 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-04-16

Brief Summary: CVE-2026-5231 — Unauthenticated Stored XSS in WP Statistics via utm_source Parameter
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

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:

  1. 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_source parameter containing a JavaScript payload (for example, an <img> tag with an onerror handler or a <script> block).

  2. The WP Statistics referral parser processes the request. Because the channel definition uses a wildcard domain, the raw utm_source value is stored in the database as the source_name without sanitization.

  3. The malicious payload sits dormant in the database.

  4. 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_name and inserts it into the chart legend or tooltip markup via innerHTML.

  5. 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

Detect & fix
what others miss

Security magnifying glass visualization