CVE-2025-13048

Official StatCounter Plugin <= 2.1.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via Nickname

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
2.1.1
Patched in
1d
Time to patch

Description

The StatCounter – Free Real Time Visitor Stats plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the user's Nickname in all versions up to, and including, 2.1.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.1.0
PublishedFebruary 18, 2026
Last updatedFebruary 19, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2025-13048**, a Stored Cross-Site Scripting (XSS) vulnerability in the **StatCounter – Free Real Time Visitor Stats** plugin for WordPress. --- ### 1. Vulnerability Summary The StatCounter plugin (up to version 2.1.0) fails to properly sanitize or esca…

Show full research plan

This exploitation research plan targets CVE-2025-13048, a Stored Cross-Site Scripting (XSS) vulnerability in the StatCounter – Free Real Time Visitor Stats plugin for WordPress.


1. Vulnerability Summary

The StatCounter plugin (up to version 2.1.0) fails to properly sanitize or escape the WordPress user's Nickname when displaying it within the plugin's administrative pages or dashboard components. Since users with Contributor roles and above can modify their own nicknames via the standard WordPress profile page, they can inject malicious JavaScript. This script is then stored in the database (wp_usermeta) and executed in the context of any user (including Administrators) who visits the affected plugin page.

2. Attack Vector Analysis

  • Vulnerable Endpoint: The standard WordPress profile update endpoint (/wp-admin/profile.php) is used to inject the payload into the nickname field.
  • Vulnerable Sink: The StatCounter settings page or dashboard widget (likely /wp-admin/admin.php?page=statcounter-config or similar).
  • Payload Parameter: nickname.
  • Authentication Level: Contributor (or higher). Contributors can edit their own profiles but typically cannot execute unfiltered_html.
  • Preconditions: The StatCounter plugin must be active and configured.

3. Code Flow

  1. Injection:
    • The Contributor user navigates to wp-admin/profile.php.
    • They submit a POST request to update their profile, including a malicious string in the nickname field.
    • WordPress core saves this to the wp_usermeta table for the user.
  2. Execution:
    • An Administrator navigates to the StatCounter settings page.
    • The plugin calls wp_get_current_user() or get_userdata() to identify the logged-in user.
    • The plugin retrieves the nickname property: $current_user->nickname.
    • The plugin echoes this value directly into the HTML response without using esc_html() or esc_attr().
    • Inferred Sink: Likely a greeting like <span>Logged in as: <?php echo $user->nickname; ?></span> or a hidden input field used for plugin settings.

4. Nonce Acquisition Strategy

To update the profile as a Contributor, a standard WordPress core nonce is required.

  1. Step 1: Use browser_navigate to go to /wp-admin/profile.php while logged in as the Contributor.
  2. Step 2: Use browser_eval to extract the profile update nonce.
    • JavaScript: document.querySelector('#_wpnonce').value
  3. Step 3: Identify the User ID.
    • JavaScript: document.querySelector('input[name="user_id"]').value
  4. Step 4: The plugin itself may have its own settings page nonce if the XSS is triggered during a plugin-specific action, but since this is a stored XSS via user meta, the primary nonce needed is for the injection phase (WordPress Profile Nonce).

5. Exploitation Strategy

Phase 1: Injection (Contributor)

  1. Authenticate as a Contributor user.
  2. Navigate to /wp-admin/profile.php.
  3. Extract Nonce: Get the _wpnonce for the update-user action.
  4. Send Update Request:
    • Method: POST
    • URL: /wp-admin/profile.php
    • Content-Type: application/x-www-form-urlencoded
    • Body:
      _wpnonce=[NONCE]&_wp_http_referer=/wp-admin/profile.php&from=profile&checkuser_id=[USER_ID]&user_id=[USER_ID]&nickname=<img src=x onerror=alert(document.domain)>&display_name=[NICKNAME_FROM_ABOVE]&action=update
      
    • Note: Ensure display_name is set to the same payload if the plugin uses display name instead of nickname.

Phase 2: Trigger (Administrator)

  1. Authenticate as an Administrator.
  2. Navigate to the StatCounter configuration page: /wp-admin/admin.php?page=statcounter-config.
  3. Observe: The payload <img src=x onerror=alert(document.domain)> executes in the Administrator's browser.

6. Test Data Setup

  1. User Creation:
    • wp user create attacker attacker@example.com --role=contributor --user_pass=password123
  2. Plugin Configuration:
    • Ensure the StatCounter plugin is installed and activated.
    • wp plugin activate official-statcounter-plugin-for-wordpress
    • (Optional) Set a dummy Project ID if required to access the settings page: wp option update statcounter_project_id 1234567.

7. Expected Results

  • The profile update should return a 302 Redirect back to profile.php?updated=1.
  • When the Administrator visits the StatCounter page, the HTTP response body should contain the raw, unescaped payload:
    ... <span>Logged in as: <img src=x onerror=alert(document.domain)></span> ...
    
  • The alert(document.domain) should execute in the browser.

8. Verification Steps

  1. Verify Storage: Use WP-CLI to check if the meta was saved correctly.
    • wp usermeta get [CONTRIBUTOR_ID] nickname
  2. Verify Output: Use http_request as an Administrator and grep for the payload.
    • http_request(url='/wp-admin/admin.php?page=statcounter-config')
    • Search for <img src=x onerror=alert(document.domain)> in the response text.

9. Alternative Approaches

  • Display Name Injection: If nickname alone doesn't trigger it, attempt injecting into the first_name or last_name fields, as plugins often use display_name (which defaults to nickname but can be configured to use others).
  • Dashboard Trigger: Check the main WordPress Dashboard (/wp-admin/index.php) for a StatCounter "Summary" widget, which might also display the user's name/nickname unescaped.
  • Blind XSS: If the nickname is passed to the StatCounter external dashboard (statcounter.com) via an iframe or API, the XSS might trigger on the external domain (though this would be out of scope for a local WP PoC). Focus on the local wp-admin pages.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Official StatCounter Plugin for WordPress (up to 2.1.0) is vulnerable to Stored Cross-Site Scripting via the user's nickname due to insufficient output escaping. Authenticated attackers with Contributor-level access or higher can inject malicious JavaScript into their nickname field on the profile page, which then executes when an administrator visits the plugin's configuration dashboard.

Exploit Outline

The exploit is carried out by an authenticated Contributor (or higher) who navigates to /wp-admin/profile.php to update their user nickname. The attacker injects a malicious payload, such as <img src=x onerror=alert(document.domain)>, into the 'Nickname' field. This payload is stored in the database. The vulnerability is triggered when an Administrator navigates to the StatCounter settings page (/wp-admin/admin.php?page=statcounter-config), where the plugin retrieves and displays the attacker's nickname without using sanitization functions like esc_html().

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.