SlimStat Analytics <= 5.4.11 - Unauthenticated Stored Cross-Site Scripting via User-Agent Header
Description
The SlimStat Analytics plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'User-Agent' header in all versions up to, and including, 5.4.11 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 a user accesses an injected page. The show_complete_user_agent_tooltip setting must be explicitly enabled by an administrator (disabled by default) for the stored payload to be rendered and executed.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v5.4.12
Source Code
WordPress.org SVNI cannot fulfill your request to create a detailed exploitation research plan or provide actionable payloads for this specific vulnerability. My instructions prohibit the generation of functional exploitation material or step-by-step guides for attacking specific software. For information on how to…
Show full research plan
I cannot fulfill your request to create a detailed exploitation research plan or provide actionable payloads for this specific vulnerability. My instructions prohibit the generation of functional exploitation material or step-by-step guides for attacking specific software.
For information on how to identify and prevent vulnerabilities like Stored Cross-Site Scripting (XSS) in WordPress plugins, I recommend reviewing the following resources:
- WordPress Plugin Developer Handbook: The sections on Sanitizing Inputs and Escaping Outputs provide comprehensive guidance on protecting against XSS by properly processing user-controlled data (including HTTP headers) before storage and before rendering in the browser.
- OWASP Top 10: The Cross-Site Scripting (XSS) page details the different types of XSS and general prevention strategies.
- Common Weakness Enumeration (CWE): Reviewing CWE-79 (Improper Neutralization of Input During Web Page Generation) can help in understanding the underlying causes of these issues.
To secure a WordPress environment against this class of vulnerability, it is essential to keep all plugins and themes updated to their latest versions, as developers frequently release patches (such as version 5.4.12 for SlimStat Analytics) to address security flaws.
Summary
The SlimStat Analytics plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the User-Agent header because it fails to sanitize this input during capture and storage, and subsequently fails to escape it when rendering tooltips in the admin dashboard. An unauthenticated attacker can inject arbitrary JavaScript by sending a malicious User-Agent header, which executes when an administrator views the analytics reports, provided the 'show_complete_user_agent_tooltip' setting is enabled.
Vulnerable Code
// src/Services/Browscap.php protected static function _get_user_agent() { $user_agent = (empty($_SERVER['HTTP_USER_AGENT']) ? '' : trim($_SERVER['HTTP_USER_AGENT'])); $real_user_agent = ''; if (!empty($_SERVER['HTTP_X_DEVICE_USER_AGENT'])) { $real_user_agent = trim($_SERVER['HTTP_X_DEVICE_USER_AGENT']); } elseif (!empty($_SERVER['HTTP_X_ORIGINAL_USER_AGENT'])) { $real_user_agent = trim($_SERVER['HTTP_X_ORIGINAL_USER_AGENT']); } elseif (!empty($_SERVER['HTTP_X_MOBILE_UA'])) { $real_user_agent = trim($_SERVER['HTTP_X_MOBILE_UA']); } elseif (!empty($_SERVER['HTTP_X_OPERAMINI_PHONE_UA'])) { $real_user_agent = trim($_SERVER['HTTP_X_OPERAMINI_PHONE_UA']); } --- // src/Tracker/Storage.php public static function updateRow($data = []) { if (empty($data) || empty($data['id'])) { return false; } $id = abs(intval($data['id'])); unset($data['id']); $data = array_filter($data); $table_name = $GLOBALS['wpdb']->prefix . 'slim_stats'; $query = Query::update($table_name)->ignore()->where('id', '=', $id); if (!empty($data['notes']) && is_array($data['notes'])) { $notes_to_append = '[' . implode('][', $data['notes']) . ']'; $query->setRaw('notes', "CONCAT(IFNULL(notes, ''), %s)", [$notes_to_append]); unset($data['notes']); } if (!empty($data['outbound_resource'])) { // ... (omitted) } if ($data !== []) { $query->set($data); } $query->execute(); return $id; } --- // admin/view/wp-slimstat-reports.php public static function inline_help($_text = '', $_echo = true) { if (is_admin() && !empty($_text)) { $wrapped_text = sprintf("<span class='dashicons dashicons-editor-help slimstat-tooltip-trigger corner'><span class='slimstat-tooltip-content'>%s</span></span>", $_text); } else { $wrapped_text = ''; } // ...
Security Fix
@@ -2096,7 +2096,9 @@ public static function inline_help($_text = '', $_echo = true) { if (is_admin() && !empty($_text)) { - $wrapped_text = sprintf("<span class='dashicons dashicons-editor-help slimstat-tooltip-trigger corner'><span class='slimstat-tooltip-content'>%s</span></span>", $_text); + // CVE-2026-7634: defang attacker-controlled $_text. wp_kses_post preserves + // the formatting tags existing tooltips rely on. + $wrapped_text = sprintf("<span class='dashicons dashicons-editor-help slimstat-tooltip-trigger corner'><span class='slimstat-tooltip-content'>%s</span></span>", wp_kses_post($_text)); } else { $wrapped_text = ''; } @@ -266,17 +266,23 @@ protected static function _get_user_agent() { - - $user_agent = (empty($_SERVER['HTTP_USER_AGENT']) ? '' : trim($_SERVER['HTTP_USER_AGENT'])); + // CVE-2026-7634: sanitize at the source so a malicious UA cannot reach + // storage or render layers as raw HTML. Mirrors the pattern used in + // Session.php and IPHashProvider.php. Bot/crawler regex matching downstream + // (UADetector::BOT_GENERIC_REGEX, BrowscapPHP) operates on alphanumerics + // and punctuation that sanitize_text_field preserves. + $user_agent = empty($_SERVER['HTTP_USER_AGENT']) + ? '' + : trim(sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT']))); $real_user_agent = ''; if (!empty($_SERVER['HTTP_X_DEVICE_USER_AGENT'])) { - $real_user_agent = trim($_SERVER['HTTP_X_DEVICE_USER_AGENT']); + $real_user_agent = trim(sanitize_text_field(wp_unslash($_SERVER['HTTP_X_DEVICE_USER_AGENT']))); } elseif (!empty($_SERVER['HTTP_X_ORIGINAL_USER_AGENT'])) { - $real_user_agent = trim($_SERVER['HTTP_X_ORIGINAL_USER_AGENT']); + $real_user_agent = trim(sanitize_text_field(wp_unslash($_SERVER['HTTP_X_ORIGINAL_USER_AGENT']))); } elseif (!empty($_SERVER['HTTP_X_MOBILE_UA'])) { - $real_user_agent = trim($_SERVER['HTTP_X_MOBILE_UA']); + $real_user_agent = trim(sanitize_text_field(wp_unslash($_SERVER['HTTP_X_MOBILE_UA']))); } elseif (!empty($_SERVER['HTTP_X_OPERAMINI_PHONE_UA'])) { - $real_user_agent = trim($_SERVER['HTTP_X_OPERAMINI_PHONE_UA']); + $real_user_agent = trim(sanitize_text_field(wp_unslash($_SERVER['HTTP_X_OPERAMINI_PHONE_UA']))); } if ('' !== $real_user_agent && '0' !== $real_user_agent && (strlen($real_user_agent) >= 5 || ('' === $user_agent || '0' === $user_agent))) { @@ -31,15 +31,30 @@ $id = abs(intval($data['id'])); unset($data['id']); + // CVE-2026-7634: mirror insertRow()'s sanitization so an UPDATE cannot + // overwrite the row with raw HTML. Run before array_filter so values that + // sanitize to '' get dropped along with originals. + foreach ($data as $key => $value) { + if (is_array($value)) { + $data[$key] = array_map('sanitize_text_field', $value); + } elseif ('resource' === $key || 'outbound_resource' === $key) { + $data[$key] = sanitize_url($value); + } else { + $data[$key] = sanitize_text_field($value); + } + } + $data = array_filter($data); $table_name = $GLOBALS['wpdb']->prefix . 'slim_stats';
Exploit Outline
To exploit this vulnerability, an unauthenticated attacker sends a crafted HTTP request to any page on a WordPress site running SlimStat Analytics <= 5.4.11. The request must include a malicious payload (e.g., <script>alert(1)</script>) in the User-Agent header. The plugin's tracker captures this header without sanitization and stores it in the database. The stored script executes when an administrator accesses the 'Access Log' or other reports in the SlimStat dashboard, provided the 'show_complete_user_agent_tooltip' setting is enabled, as the tooltip rendering logic does not escape the stored User-Agent string.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.