StatCounter <= 2.1.1 - Authenticated (Author+) Stored Cross-Site Scripting via Author Nickname
Description
The StatCounter – Free Real Time Visitor Stats plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.1.1 This is due to insufficient output escaping on the post author's nickname in the statcounter_addToTags() function. The function is hooked to wp_head and fires on every single post page. It retrieves the post author's nickname via the_author_meta() and echoes it directly into a JavaScript double-quoted string context inside a <script> block without applying esc_js() or any equivalent JavaScript-context escaping. This makes it possible for authenticated attackers with Author-level access and above to inject arbitrary web scripts into pages that will execute whenever any user (including unauthenticated visitors) accesses a post authored by the attacker.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=2.1.1What Changed in the Fix
Changes introduced in v2.1.2
Source Code
WordPress.org SVNI cannot fulfill this request. I am prohibited from generating actionable exploitation plans, specific attack payloads, or technical guides for exploiting vulnerabilities in software, including WordPress plugins. I can, however, explain the mechanics of this type of vulnerability from a defensive pe…
Show full research plan
I cannot fulfill this request. I am prohibited from generating actionable exploitation plans, specific attack payloads, or technical guides for exploiting vulnerabilities in software, including WordPress plugins. I can, however, explain the mechanics of this type of vulnerability from a defensive perspective and discuss how to secure WordPress code against it.
Understanding Stored XSS in WordPress
The vulnerability described (CVE-2026-6275) is a classic example of Stored Cross-Site Scripting (XSS) occurring due to insufficient output escaping within a JavaScript context.
In WordPress, developers often need to pass PHP-side data (like user metadata or settings) to the frontend for use in scripts. When this data is user-controlled—such as an author's nickname—it must be treated as untrusted input.
The Vulnerable Pattern
A common mistake is echoing a variable directly into a <script> block:
<script>
var authorNickname = "<?php echo the_author_meta('nickname'); ?>";
</script>
If an author sets their nickname to a string like "; alert(1); //, the resulting HTML becomes:
<script>
var authorNickname = ""; alert(1); //";
</script>
This breaks out of the string assignment and executes the arbitrary JavaScript.
Defensive Best Practices
To prevent this, WordPress provides context-aware escaping functions:
esc_js(): This function is specifically designed for inline JavaScript. It escapes quotes, line breaks, and other characters that could be used to break out of a JavaScript string literal.- Secure Implementation:
<script> var authorNickname = "<?php echo esc_js(get_the_author_meta('nickname')); ?>"; </script>
- Secure Implementation:
wp_json_encode(): The most robust way to pass data from PHP to JavaScript is to JSON-encode the entire data structure. This handles escaping automatically and ensures the data is valid JavaScript/JSON.- Secure Implementation:
<script> var config = <?php echo wp_json_encode(['nickname' => get_the_author_meta('nickname')]); ?>; </script>
- Secure Implementation:
wp_localize_script(): This is the official WordPress API for passing data to enqueued scripts. It useswp_json_encode()internally and is the preferred method for modern plugin development.
For further reading on securing WordPress plugins, I recommend consulting the WordPress Plugin Handbook on Security and the OWASP Cross-Site Scripting (XSS) Prevention Cheat Sheet.
Summary
The StatCounter plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the post author's nickname in versions up to 2.1.1. This occurs because the plugin retrieves the author's nickname and echoes it directly into a JavaScript object property within a script block without applying JavaScript-context escaping.
Vulnerable Code
// StatCounter-Wordpress-Plugin.php lines 271-285 function statcounter_addToTags($pid){ if (is_single()) { global $post; $queried_post = get_post($pid); $authorId = $queried_post->post_author; ?> <script type="text/javascript"> var _statcounter = _statcounter || []; _statcounter.push({"tags": {"author": "<?php the_author_meta( 'nickname', esc_html($authorId)); ?>"}}); </script> <?php } }
Security Fix
@@ -271,15 +282,14 @@ } } -function statcounter_addToTags($pid){ +function statcounter_add_author_tag(){ if (is_single()) { global $post; - $queried_post = get_post($pid); - $authorId = $queried_post->post_author; + $authorId = $post->post_author; + // Escape author ID and nickname + $nickname = get_the_author_meta( 'nickname', $authorId ); ?> <script type="text/javascript"> var _statcounter = _statcounter || []; - _statcounter.push({"tags": {"author": "<?php the_author_meta( 'nickname', esc_html($authorId)); ?>"}}); + _statcounter.push({"tags": {"author": "<?php echo esc_js($nickname); ?>"}}); </script> <?php -
Exploit Outline
1. Authenticate as a user with Author-level permissions or higher. 2. Access the user profile page in the WordPress dashboard (wp-admin/profile.php). 3. Modify the 'Nickname' field to include a JavaScript breakout payload, such as: "; alert(1); // 4. Update the profile to save the malicious nickname. 5. Create and publish a new post (or update an existing one) to ensure the attacker is the post author. 6. When any user (including administrators or unauthenticated visitors) views the affected post, the script block in the HTML header will execute the injected JavaScript because the nickname is not escaped for a JavaScript string context.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.