CVE-2026-2936

Visitor Traffic Real Time Statistics <= 8.4 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
8.5
Patched in
1d
Time to patch

Description

The Visitor Traffic Real Time Statistics plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'page_title' parameter in all versions up to, and including, 8.4 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever an admin user accesses the Traffic by Title section.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=8.4
PublishedApril 3, 2026
Last updatedApril 4, 2026

What Changed in the Fix

Changes introduced in v8.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-2936 ## 1. Vulnerability Summary The **Visitor Traffic Real Time Statistics** plugin (<= 8.4) contains a stored Cross-Site Scripting (XSS) vulnerability via the `page_title` parameter. The vulnerability exists because the plugin's visitor tracking logic fails…

Show full research plan

Exploitation Research Plan - CVE-2026-2936

1. Vulnerability Summary

The Visitor Traffic Real Time Statistics plugin (<= 8.4) contains a stored Cross-Site Scripting (XSS) vulnerability via the page_title parameter. The vulnerability exists because the plugin's visitor tracking logic fails to sanitize the page title before storing it in the database, and the admin dashboard subsequently renders this title using the dangerous innerHTML property in its JavaScript-based data tables.

An unauthenticated attacker can send a crafted AJAX request to the tracking endpoint to inject a malicious script. When an administrator views the Traffic by Title section in the plugin's dashboard, the script executes in their browser context.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • Action: ahcfree_track_visitor (Unauthenticated)
  • Vulnerable Parameter: page_title
  • Preconditions: The plugin must be active.
  • Authentication: No authentication is required for the tracking endpoint (wp_ajax_nopriv_ahcfree_track_visitor).

3. Code Flow

  1. Entry Point: The attacker sends a POST request to admin-ajax.php with action=ahcfree_track_visitor.
  2. Tracking Logic: The AJAX handler (inferred to be in init.php or Visitors-Traffic-Real-Time-Statistics.php) instantiates the WPHitsCounter class.
  3. Source (WPHitsCounter.php):
    • The constructor __construct($page_id, $page_title = NULL, $post_type = NULL) receives the $page_title parameter directly from the AJAX handler.
    • At line 63: $this->pageTitle = $page_title; — The input is assigned to the class property without any sanitization (unlike userAgent which uses ahc_free_sanitize_text_or_array_field).
  4. Storage:
    • traceVisitorHit() calls $this->updateTitleTraffic($this->pageId, $this->pageTitle) (line 156).
    • This function stores the raw payload in the ahc_title_traffic table (inferred database table name).
  5. Sink (js/ahcfree_js_scripts.js):
    • The admin views the "Traffic by Title" page (admin.php?page=ahc_hits_counter_menu_free).
    • The DataTable traffic_by_title (line 141) fetches data via AJAX from action=traffic_by_title.
    • In the render function for the hits column (lines 160-184):
      if (row.til_page_title) {
          var tempDiv = document.createElement('div');
          tempDiv.innerHTML = row.til_page_title; // DANGEROUS SINK: Executes injected HTML
          pageTitle = tempDiv.textContent || tempDiv.innerText || 'Unknown';
      }
      
    • Even if tempDiv is never appended to the DOM, setting innerHTML with a payload like <img src=x onerror=alert(1)> triggers immediate execution.

4. Nonce Acquisition Strategy

The ahcfree_track_visitor action is used for frontend visitor tracking and does not require a nonce.

  • Evidence: In Visitors-Traffic-Real-Time-Statistics.php (lines 80-97), the function ahcfree_send_first_visit_request() generates a script that sends the tracking request. The xhttp.send payload (line 93) contains only action, page_id, page_title, post_type, referer, useragent, servername, hostname, and request_uri. No nonce is included.

5. Exploitation Strategy

  1. Payload: Use an <img> tag with an onerror event to trigger the XSS.
    • page_title: <img src=x onerror="alert('CVE-2026-2936_XSS')">
  2. Request: Perform an unauthenticated POST request to the AJAX endpoint.
  3. Trigger: Log in as an administrator and navigate to the plugin's dashboard.

HTTP Request Details

  • Method: POST
  • URL: http://<target>/wp-admin/admin-ajax.php
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: ahcfree_track_visitor
    • page_id: 9999 (A unique ID ensures the hit is recorded)
    • page_title: <img src=x onerror="alert('CVE-2026-2936_XSS')">
    • post_type: page (Must be post, product, or page to satisfy the check in traceVisitorHit)
    • request_uri: tracking-test

6. Test Data Setup

  1. Install and activate the plugin "Visitor Traffic Real Time Statistics" version 8.4.
  2. No specific pages or shortcodes are required since the ahcfree_track_visitor endpoint accepts arbitrary page_id and page_title values from the request.

7. Expected Results

  • The tracking request should return a successful response (likely 0 or empty if standard WordPress AJAX response, or a specific success message).
  • The payload will be stored in the database.
  • When the admin navigates to wp-admin/admin.php?page=ahc_hits_counter_menu_free, the browser will execute the alert.

8. Verification Steps

  1. Check Database: Use WP-CLI to verify the payload is stored.
    wp db query "SELECT til_page_title FROM wp_ahc_title_traffic WHERE til_page_id = 9999"
    
  2. Simulate Admin View: Use the browser_navigate tool to log in as admin and visit the dashboard page.
  3. Observe Execution: Confirm the alert is triggered via Playwright's dialog handling.

9. Alternative Approaches

If the DataTable render function is patched, look for other sinks in the admin dashboard:

  • The dashboard might display the "Top Pages" or "Traffic by Title" in a different widget or export function.
  • In js/ahcfree_js_scripts.js (line 176), the title is also used in a data-page-title attribute:
    data-page-title="${pageTitle.replace(/"/g, '&quot;')}"
    
    If the pageTitle extraction failed to strip the payload, it might be injectable here if the quotes are not handled perfectly, though the innerHTML sink is the primary target.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Visitor Traffic Real Time Statistics plugin is vulnerable to unauthenticated stored Cross-Site Scripting via the 'page_title' parameter. Attackers can inject malicious scripts into the tracking database by sending a crafted AJAX request, which then executes when an administrator views the Traffic by Title section in the dashboard.

Vulnerable Code

// WPHitsCounter.php (~line 63)
public function __construct($page_id, $page_title = NULL, $post_type = NULL)
{
    // ... (truncated)
    $this->pageTitle = $page_title;
    // ... (truncated)
}

---

// js/ahcfree_js_scripts.js (~line 160)
if (row.til_page_title) {
    var tempDiv = document.createElement('div');
    tempDiv.innerHTML = row.til_page_title; // DANGEROUS SINK
    pageTitle = tempDiv.textContent || tempDiv.innerText || 'Unknown';
}

---

// functions.php (~line 3292 in 8.4)
$page_id = intval($_POST['page_id']);
$page_title = ahc_free_sanitize_text_or_array_field($_POST['page_title']);

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/visitors-traffic-real-time-statistics/8.4/functions.php /home/deploy/wp-safety.org/data/plugin-versions/visitors-traffic-real-time-statistics/8.5/functions.php
--- /home/deploy/wp-safety.org/data/plugin-versions/visitors-traffic-real-time-statistics/8.4/functions.php	2026-01-31 09:32:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/visitors-traffic-real-time-statistics/8.5/functions.php	2026-02-21 04:42:48.000000000 +0000
@@ -3289,7 +3301,11 @@
 
 
             $page_id = intval($_POST['page_id']);
-            $page_title = ahc_free_sanitize_text_or_array_field($_POST['page_title']);
+           // $page_title = ahc_free_sanitize_text_or_array_field($_POST['page_title']);
+           
+            $page_title_raw = isset($_POST['page_title']) ? wp_unslash($_POST['page_title']) : '';
+			$page_title_decoded = html_entity_decode($page_title_raw, ENT_QUOTES | ENT_HTML5, 'UTF-8');
+			$page_title = sanitize_text_field(wp_strip_all_tags($page_title_decoded, true));
             $post_type = ahc_free_sanitize_text_or_array_field($_POST['post_type']);
             $_SERVER['HTTP_REFERER'] = ahc_free_sanitize_text_or_array_field($_POST['referer']);
             $_SERVER['HTTP_USER_AGENT'] = ahc_free_sanitize_text_or_array_field($_POST['useragent']);
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/visitors-traffic-real-time-statistics/8.4/js/ahcfree_js_scripts.js /home/deploy/wp-safety.org/data/plugin-versions/visitors-traffic-real-time-statistics/8.5/js/ahcfree_js_scripts.js
--- /home/deploy/wp-safety.org/data/plugin-versions/visitors-traffic-real-time-statistics/8.4/js/ahcfree_js_scripts.js	2026-01-31 09:32:52.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/visitors-traffic-real-time-statistics/8.5/js/ahcfree_js_scripts.js	2026-02-21 04:42:48.000000000 +0000
@@ -404,7 +404,7 @@
                 }
             </style>
             <div class="traffic-header-enhanced">
-                <div class="page-title">${pageTitle}</div>
+                <div class="page-title"></div>
                 <div class="hits">${hits.toLocaleString()} hits</div>
             </div>
             <div class="traffic-image-container">
@@ -416,6 +416,8 @@
 
 			// Update modal content and title
 			jQuery('#TrafficStatsModal .modal-body').html(headerHtml);
+			jQuery('#TrafficStatsModal .modal-body').html(headerHtml);
+			jQuery('#TrafficStatsModal .modal-body .page-title').text(pageTitle);
 			jQuery('#TrafficStatsModal .modal-title').text('Page Statistics: ' + pageTitle);

Exploit Outline

The exploit targets the unauthenticated AJAX tracking endpoint `ahcfree_track_visitor`. An attacker sends a POST request to `wp-admin/admin-ajax.php` with the `action` parameter set to `ahcfree_track_visitor`. The payload is placed in the `page_title` parameter (e.g., `<img src=x onerror=alert(1)>`). Because the plugin fails to adequately sanitize this input before storing it in the database and subsequently uses `innerHTML` to render the data in the admin's 'Traffic by Title' DataTable, the script executes in the context of any administrator viewing the statistics dashboard.

Check if your site is affected.

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