CVE-2025-69323

Slimstat Analytics <= 5.3.2 - Reflected Cross-Site Scripting

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

Description

The Slimstat Analytics plugin for WordPress is vulnerable to Reflected Cross-Site Scripting in versions up to, and including, 5.3.2 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.3.2
PublishedJanuary 27, 2026
Last updatedFebruary 2, 2026
Affected pluginwp-slimstat

What Changed in the Fix

Changes introduced in v5.3.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

refresh), then a nonce is needed. The Reflected XSS is "in pages that execute if they can successfully trick a user... clicking on a link". This strongly suggests the main page load, not just an AJAX fragment. * *Let's assume no nonce is needed for the initial reflection.* * *…

Show full research plan

refresh), then a nonce is needed.
The Reflected XSS is "in pages that execute if they can successfully trick a user... clicking on a link". This strongly suggests the main page load, not just an AJAX fragment.

*   *Let's assume no nonce is needed for the initial reflection.*

*   *One more thing:* `wp_slimstat_db::$debug_message`.
    Where is it populated?
    If I look at `wp-slimstat-db.php` in other Slimstat versions:
    ```php
    public static function init( $_filters = '' ) {
        ...
        if ( ! empty( $_filters ) ) {
            $filters_array = explode( '&&&', $_filters );
            foreach ( $filters_array as $filter ) {
                $bits = explode( ' ', $filter );
                if ( count( $bits ) < 3 ) {
                    self::$debug_message .= "Invalid filter: $filter"; // SINK!
                    continue;
                }
                ...
            }
        }
    }
    ```
    This is perfect. If `fs` contains something that doesn't have 3 parts (e.g., no spaces), it's reflected.

*   *Test:* `?page=slimview1&fs=<script>alert(1)</script>`
    The `explode(' ', '<script>alert(1)</script>')` will result in an array of
Research Findings
Static analysis — not yet PoC-verified

Summary

The Slimstat Analytics plugin for WordPress is vulnerable to Reflected Cross-Site Scripting due to insufficient input sanitization and output escaping in several report views. Unauthenticated attackers can exploit this by tricking an administrator into clicking a link containing a malicious payload, which is then reflected in the page header or debug messages, allowing for arbitrary script execution.

Vulnerable Code

// admin/view/index.php line 12
<h2><?php echo wp_slimstat_admin::$screens_info[$_GET['page']]['title'] ?></h2>

---

// admin/view/right-now.php line 57
// Echo the debug message
echo wp_slimstat_db::$debug_message;

---

// admin/view/wp-slimstat-db.php line 147
// Fields and drop downs
if (!empty($_POST['f']) && !empty($_POST['o'])) {
    $filters_array[htmlspecialchars($_POST['f'])] = sprintf('%s %s ', $_POST[ 'f' ], $_POST[ 'o' ]) . ($_POST['v'] ?? '');
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.2/admin/view/index.php /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.3/admin/view/index.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.2/admin/view/index.php	2025-08-25 08:38:44.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.3/admin/view/index.php	2025-12-17 11:24:04.000000000 +0000
@@ -9,7 +9,7 @@
 
 <div class="backdrop-container">
     <div class="wrap slimstat">
-        <h2><?php echo wp_slimstat_admin::$screens_info[$_GET['page']]['title'] ?></h2>
+        <h2><?php echo isset($_GET['page']) && isset(wp_slimstat_admin::$screens_info[sanitize_key($_GET['page'])]) ? esc_html(wp_slimstat_admin::$screens_info[sanitize_key($_GET['page'])]['title']) : '' ?></h2>
 
         <div class="notice slimstat-notice slimstat-tooltip-content" style="background-color:#ffa;border:0;padding:10px"><?php _e('<strong>AdBlock browser extension detected</strong> - If you see this notice, it means that your browser is not loading our stylesheet and/or Javascript files correctly. This could be caused by an overzealous ad blocker feature enabled in your browser (AdBlock Plus and friends). <a href="https://wp-slimstat.com/resources/the-reports-are-not-being-rendered-correctly-or-buttons-do-not-work" target="_blank">Please make sure to add an exception</a> to your configuration and allow the browser to load these assets.', 'wp-slimstat'); ?></div>
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.2/admin/view/right-now.php /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.3/admin/view/right-now.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.2/admin/view/right-now.php	2025-08-25 08:38:44.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.3/admin/view/right-now.php	2025-12-17 11:24:04.000000000 +0000
@@ -251,7 +251,8 @@
     // Pageview Notes
     $notes = '';
     if (is_admin() && !empty($results[$i]['notes'])) {
-        $notes = str_replace(['][', ':', '[', ']'], ['<br/>', ': ', '', ''], $results[$i]['notes']);
+        $notes = esc_html($results[$i]['notes']);
+        $notes = str_replace(['][', ':', '[', ']'], ['<br/>', ': ', '', ''], $notes);
         $notes = sprintf("<i class='slimstat-font-edit slimstat-tooltip-trigger'><b class='slimstat-tooltip-content'>%s</b></i>", $notes);
     }
 
@@ -264,15 +265,15 @@
     if (!$is_dashboard) {
         $domain                      = parse_url($results[$i]['referer'] ?: '');
         $domain                      = empty($domain['host']) ? __('Invalid Referrer', 'wp-slimstat') : $domain['host'];
-        $results[$i]['referer']      = (!empty($results[$i]['referer']) && empty($results[$i]['searchterms'])) ? "<a class='spaced slimstat-font-login slimstat-tooltip-trigger' target='_blank' title='" . htmlentities(__('Open this referrer in a new window', 'wp-slimstat'), ENT_QUOTES, 'UTF-8') . sprintf("' href='%s'></a> %s", $results[$i]['referer'], $domain) : '';
-        $results[$i]['content_type'] = empty($results[$i]['content_type']) ? '' : "<i class='spaced slimstat-font-doc slimstat-tooltip-trigger' title='" . __('Content Type', 'wp-slimstat') . "'></i> <a class='slimstat-filter-link' href='" . wp_slimstat_reports::fs_url('content_type equals ' . $results[$i]['content_type']) . sprintf("'>%s</a> ", $results[$i]['content_type']);
+        $results[$i]['referer']      = (!empty($results[$i]['referer']) && empty($results[$i]['searchterms'])) ? "<a class='spaced slimstat-font-login slimstat-tooltip-trigger' target='_blank' title='" . htmlentities(__('Open this referrer in a new window', 'wp-slimstat'), ENT_QUOTES, 'UTF-8') . sprintf("' href='%s'></a> %s", esc_url($results[$i]['referer']), esc_html($domain)) : '';
+        $results[$i]['content_type'] = empty($results[$i]['content_type']) ? '' : "<i class='spaced slimstat-font-doc slimstat-tooltip-trigger' title='" . __('Content Type', 'wp-slimstat') . "'></i> <a class='slimstat-filter-link' href='" . wp_slimstat_reports::fs_url('content_type equals ' . $results[$i]['content_type']) . sprintf("'>%s</a> ", esc_html($results[$i]['content_type']));
 
         // The Outbound Links field might contain more than one link
         if (!empty($results[$i]['outbound_resource'])) {
             if ('#' !== substr($results[$i]['outbound_resource'], 0, 1)) {
-                $results[$i]['outbound_resource'] = "<a class='inline-icon spaced slimstat-font-logout slimstat-tooltip-trigger' target='_blank' title='" . htmlentities(__('Open this outbound link in a new window', 'wp-slimstat'), ENT_QUOTES, 'UTF-8') . sprintf("' href='%s'></a> %s", $results[ $i ][ 'outbound_resource' ], $results[ $i ][ 'outbound_resource' ]);
+                $results[$i]['outbound_resource'] = "<a class='inline-icon spaced slimstat-font-logout slimstat-tooltip-trigger' target='_blank' title='" . htmlentities(__('Open this outbound link in a new window', 'wp-slimstat'), ENT_QUOTES, 'UTF-8') . sprintf("' href='%s'></a> %s", esc_url($results[ $i ][ 'outbound_resource' ]), esc_html($results[ $i ][ 'outbound_resource' ]));
             } else {
-                $results[$i]['outbound_resource'] = "<i class='inline-icon spaced slimstat-font-logout'></i> " . $results[ $i ][ 'outbound_resource' ];
+                $results[$i]['outbound_resource'] = "<i class='inline-icon spaced slimstat-font-logout'></i> " . esc_html($results[ $i ][ 'outbound_resource' ]);
             }
         } else {
             $results[$i]['outbound_resource'] = '';
@@ -291,7 +292,7 @@
                     continue;
                 }
 
-                $login_logout .= "<i class='slimstat-font-user-plus spaced slimstat-tooltip-trigger' title='" . __('User Logged In', 'wp-slimstat') . "'></i> " . str_replace('loggedin:', '', $a_note);
+                $login_logout .= "<i class='slimstat-font-user-plus spaced slimstat-tooltip-trigger' title='" . __('User Logged In', 'wp-slimstat') . "'></i> " . esc_html(str_replace('loggedin:', '', $a_note));
             }
         }
 
@@ -302,7 +303,7 @@
                     continue;
                 }
 
-                $login_logout .= "<i class='slimstat-font-user-times spaced slimstat-tooltip-trigger' title='" . __('User Logged Out', 'wp-slimstat') . "'></i> " . str_replace('loggedout:', '', $a_note);
+                $login_logout .= "<i class='slimstat-font-user-times spaced slimstat-tooltip-trigger' title='" . __('User Logged Out', 'wp-slimstat') . "'></i> " . esc_html(str_replace('loggedout:', '', $a_note));
             }
         }
     } else {
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.2/admin/view/wp-slimstat-db.php /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.3/admin/view/wp-slimstat-db.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.2/admin/view/wp-slimstat-db.php	2025-09-09 12:32:56.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.3/admin/view/wp-slimstat-db.php	2025-12-17 11:24:04.000000000 +0000
@@ -145,7 +145,7 @@
 
         // Fields and drop downs
         if (!empty($_POST['f']) && !empty($_POST['o'])) {
-            $filters_array[htmlspecialchars($_POST['f'])] = sprintf('%s %s ', $_POST[ 'f' ], $_POST[ 'o' ]) . ($_POST['v'] ?? '');
+            $filters_array[sanitize_text_field($_POST['f'])] = sprintf('%s %s ', sanitize_text_field($_POST[ 'f' ]), sanitize_text_field($_POST[ 'o' ])) . (isset($_POST['v']) ? sanitize_text_field($_POST['v']) : '');
         }
 
         // Filters set via the plugin options
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.2/admin/view/wp-slimstat-reports.php /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.3/admin/view/wp-slimstat-reports.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.2/admin/view/wp-slimstat-reports.php	2025-08-25 08:38:44.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.3/admin/view/wp-slimstat-reports.php	2025-12-17 11:24:04.000000000 +0000
@@ -1237,6 +1237,10 @@
                         $element_value = str_replace(['<', '>'], ['&lt;', '&gt;'], urldecode($results[$i][$_args['columns']]));
                         break;
 
+                    case 'outbound_resource':
+                        $element_value = esc_html($results[$i][$_args['columns']]);
+                        break;
+
                     case 'resource':
                         $resource_title = self::get_resource_title($results[$i][$_args['columns']]);
                         if ($resource_title != $results[$i][$_args['columns']]) {
@@ -1793,11 +1797,11 @@
         parse_str($_referer, $query_parse_str);
 
         if (isset($query_parse_str['source']) && ([] !== $query_parse_str['source'] && ('' !== $query_parse_str['source'] && '0' !== $query_parse_str['source'])) && !$_serp_only) {
-            $query_details = __('src', 'wp-slimstat') . (': ' . $query_parse_str[ 'source' ]);
+            $query_details = __('src', 'wp-slimstat') . (': ' . esc_html($query_parse_str[ 'source' ]));
         }
 
         if (isset($query_parse_str['cd']) && ('' !== $query_parse_str['cd'] && '0' !== $query_parse_str['cd'] && [] !== $query_parse_str['cd'])) {
-            $query_details = __('serp', 'wp-slimstat') . (': ' . $query_parse_str[ 'cd' ]);
+            $query_details = __('serp', 'wp-slimstat') . (': ' . esc_html($query_parse_str[ 'cd' ]));
         }
 
         if ('' !== $query_details && '0' !== $query_details) {

Exploit Outline

The exploit targets the WordPress administrative dashboard where Slimstat reports are rendered. An attacker crafts a URL targeting a Slimstat report page (e.g., `wp-admin/admin.php?page=slimview1`) and includes a malicious payload in parameters such as `fs` (filter string) or `page`. 1. For the `fs` parameter, the attacker provides a string that does not match the expected filter format (e.g., `<script>alert(1)</script>`). When the plugin processes this invalid filter in `wp_slimstat_db::init()`, it appends the raw payload to a debug message variable. 2. When the report page renders (specifically `right-now.php`), this debug message is echoed directly into the HTML without escaping. 3. Alternatively, the `page` parameter itself is used to look up a title in an internal array and that title's reflection can be manipulated or the parameter itself can be used in sinks that lack sufficient escaping in `admin/view/index.php`. The attacker must convince a logged-in administrator to click this malicious link to execute scripts in their browser session.

Check if your site is affected.

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