Plugin Directory

Changeset 3401545


Ignore:
Timestamp:
11/24/2025 05:41:38 AM (4 months ago)
Author:
mostafa.s1990
Message:

Update to version 5.3.2 from GitHub

Location:
wp-slimstat
Files:
10 edited
1 copied

Legend:

Unmodified
Added
Removed
  • wp-slimstat/tags/5.3.2/CHANGELOG.md

    r3358591 r3401545  
     1= 5.3.2 - 2025-11-24 =
     2- Fix: Minor improvements & Hardened plugin security.
     3
    14= 5.3.1 - 2025-09-09 =
    25- **Fix**: Resolved "Invalid Date, NaN" error in monthly charts for 12-month ranges.
  • wp-slimstat/tags/5.3.2/languages/wp-slimstat.pot

    r3358591 r3401545  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: SlimStat Analytics 5.3.1\n"
     5"Project-Id-Version: SlimStat Analytics 5.3.2\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-slimstat\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2025-09-09T12:28:59+00:00\n"
     12"POT-Creation-Date: 2025-11-24T05:39:19+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    14 "X-Generator: WP-CLI 2.11.0\n"
     14"X-Generator: WP-CLI 2.12.0\n"
    1515"X-Domain: wp-slimstat\n"
    1616
     
    306306
    307307#: admin/config/index.php:187
     308#, php-format
    308309msgid "You are currently using version %s."
    309310msgstr ""
     
    971972
    972973#: admin/index.php:901
     974#, php-format
    973975msgid "Pageviews in the last %s days"
    974976msgstr ""
    975977
    976978#: admin/index.php:903
     979#, php-format
    977980msgid "Unique IPs in the last %s days"
    978981msgstr ""
     
    12631266
    12641267#: admin/view/addons.php:23
     1268#, php-format
    12651269msgid "There was an error retrieving the add-ons list from the server. Please try again later. Error Message: %s"
    12661270msgstr ""
     
    12791283
    12801284#: admin/view/addons.php:42
     1285#, php-format
    12811286msgid "This list is refreshed once daily: <a href=\"%s&amp;force_refresh=true\" class=\"noslimstat\">click here</a> to clear the cache."
    12821287msgstr ""
     
    13211326#: admin/view/index.php:66
    13221327#: admin/view/wp-slimstat-db.php:812
    1323 #: src/Modules/Chart.php:101
     1328#: src/Modules/Chart.php:115
    13241329msgid "Today"
    13251330msgstr ""
     
    14111416#: admin/view/index.php:138
    14121417#: admin/view/wp-slimstat-reports.php:1724
     1418#, php-format
    14131419msgid "GeoIP collection is not enabled. Please go to <a href='%s' class='noslimstat'>setting page</a> to enable GeoIP for getting more information and location (country) from the visitor."
    14141420msgstr ""
    14151421
    14161422#: admin/view/index.php:142
     1423#, php-format
    14171424msgid "Install our <a href='%s' class='noslimstat'>Browscap Library</a> to identify your visitors' browser and operating system."
    14181425msgstr ""
    14191426
    14201427#: admin/view/index.php:147
     1428#, php-format
    14211429msgid "A caching plugin might be enabled on your website. Please <a href='%s' target='_blank' class='noslimstat'>make sure to configure</a> Slimstat Analytics accordingly, to get accurate information."
    14221430msgstr ""
     
    24842492
    24852493#: admin/view/wp-slimstat-reports.php:1015
     2494#, php-format
    24862495msgid "Showing %s - %s of %s"
    24872496msgstr ""
     
    47834792
    47844793#: src/Exception/LogException.php:20
     4794#, php-format
    47854795msgid "Exception occurred: [Code %d] %s at %s:%d"
    47864796msgstr ""
    47874797
    4788 #: src/Modules/Chart.php:48
     4798#: src/Modules/Chart.php:47
     4799msgid "Insufficient permissions"
     4800msgstr ""
     4801
     4802#: src/Modules/Chart.php:54
    47894803msgid "Invalid granularity"
    47904804msgstr ""
    47914805
    4792 #: src/Modules/Chart.php:99
     4806#: src/Modules/Chart.php:113
    47934807msgid "-- Previous Period"
    47944808msgstr ""
    47954809
    4796 #: src/Modules/Chart.php:100
     4810#: src/Modules/Chart.php:114
    47974811msgid "Click Tap “Previous Period” to hide or show the previous period line."
    47984812msgstr ""
    47994813
    4800 #: src/Modules/Chart.php:102
     4814#: src/Modules/Chart.php:116
    48014815msgid "30 Days ago"
    48024816msgstr ""
    48034817
    4804 #: src/Modules/Chart.php:103
     4818#: src/Modules/Chart.php:117
    48054819msgid "Day ago"
    48064820msgstr ""
    48074821
    4808 #: src/Modules/Chart.php:104
     4822#: src/Modules/Chart.php:118
    48094823msgid "Year ago"
    48104824msgstr ""
    48114825
    4812 #: src/Modules/Chart.php:105
     4826#: src/Modules/Chart.php:119
    48134827msgid "Now"
     4828msgstr ""
     4829
     4830#: src/Modules/Chart.php:383
     4831#: src/Modules/Chart.php:400
     4832msgid "Invalid SQL function in chart data expression"
     4833msgstr ""
     4834
     4835#: src/Modules/Chart.php:387
     4836#: src/Modules/Chart.php:404
     4837msgid "Invalid column name in chart data expression"
     4838msgstr ""
     4839
     4840#: src/Modules/Chart.php:412
     4841msgid "Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed."
    48144842msgstr ""
    48154843
     
    48594887
    48604888#: src/Services/GeoIP.php:178
     4889#, php-format
    48614890msgid "Error Creating GeoIP Database Directory. Ensure Web Server Has Directory Creation Permissions in: %s"
    48624891msgstr ""
    48634892
    48644893#: src/Services/GeoIP.php:182
     4894#, php-format
    48654895msgid "Error Setting Permissions for GeoIP Database Directory. Check Write Permissions for Directories in: %s"
    48664896msgstr ""
    48674897
    48684898#: src/Services/GeoIP.php:190
     4899#, php-format
    48694900msgid "Error Downloading GeoIP Database from: %1$s - %2$s"
    48704901msgstr ""
     
    48754906
    48764907#: src/Services/GeoIP.php:223
     4908#, php-format
    48774909msgid "Error Opening Downloaded GeoIP Database for Reading: %s"
    48784910msgstr ""
    48794911
    48804912#: src/Services/GeoIP.php:227
     4913#, php-format
    48814914msgid "Error Opening Destination GeoIP Database for Writing: %s"
    48824915msgstr ""
     
    48874920
    48884921#: src/Services/GeoIP.php:248
     4922#, php-format
    48894923msgid "Error: %1$s"
    48904924msgstr ""
     
    49354969
    49364970#: wp-slimstat.php:581
     4971#, php-format
    49374972msgid "Attempted XSS Injection: %s"
    49384973msgstr ""
     
    49715006
    49725007#: wp-slimstat.php:1144
     5008#, php-format
    49735009msgid "This parameter is used to filter a given dimension (resources, browsers, operating systems, etc) so that it satisfies certain conditions (i.e.: browser contains Chrome). Please make sure to urlencode this value, and to use the usual filter format: browser contains Chrome&&&referer contains slim (encoded: browser%20contains%20Chrome%26%26%26referer%20contains%20slim)"
    49745010msgstr ""
  • wp-slimstat/tags/5.3.2/readme.txt

    r3358591 r3401545  
    66Requires PHP: 7.4
    77Tested up to: 6.8
    8 Stable tag: 5.3.1
     8Stable tag: 5.3.2
    99License: GPL-2.0+
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    7474
    7575== Changelog ==
     76= 5.3.2 - 2025-11-24 =
     77- Fix: Minor improvements & Hardened plugin security.
     78
    7679= 5.3.1 - 2025-09-09 =
    7780- **Fix**: Resolved "Invalid Date, NaN" error in monthly charts for 12-month ranges.
  • wp-slimstat/tags/5.3.2/src/Modules/Chart.php

    r3349531 r3401545  
    4242        check_ajax_referer('slimstat_chart_nonce', 'nonce');
    4343
     44        // Additional capability check - users must be able to view stats
     45        $minimum_capability = 'read';
     46        if (!current_user_can($minimum_capability)) {
     47            wp_send_json_error(['message' => __('Insufficient permissions', 'wp-slimstat')]);
     48        }
     49
    4450        $args        = isset($_POST['args']) ? json_decode(stripslashes($_POST['args']), true) : [];
    4551        $granularity = isset($_POST['granularity']) ? sanitize_text_field($_POST['granularity']) : 'daily';
     
    4753        if (!in_array($granularity, ['yearly', 'monthly', 'weekly', 'daily', 'hourly'], true)) {
    4854            wp_send_json_error(['message' => __('Invalid granularity', 'wp-slimstat')]);
     55        }
     56       
     57        // Validate and sanitize start/end timestamps
     58        if (isset($args['start'])) {
     59            $args['start'] = absint($args['start']);
     60        }
     61        if (isset($args['end'])) {
     62            $args['end'] = absint($args['end']);
    4963        }
    5064
     
    8094                'translations' => $chart->translations,
    8195            ]);
    82         } catch (Exception $exception) {
     96        } catch (\Exception $exception) {
    8397            wp_send_json_error(['message' => $exception->getMessage()]);
    8498        }
     
    217231        $data1 = $args['chart_data']['data1'] ?? '';
    218232        $data2 = $args['chart_data']['data2'] ?? '';
    219         $start = $args['start'];
    220         $end   = $args['end'];
     233       
     234        // Validate SQL expressions to prevent SQL injection
     235        $data1 = $this->validateSqlExpression($data1);
     236        $data2 = $this->validateSqlExpression($data2);
     237       
     238        // Ensure timestamps are integers (defense in depth)
     239        $start = absint($args['start']);
     240        $end   = absint($args['end']);
     241        $prevStart = absint($prevArgs['start']);
     242        $prevEnd = absint($prevArgs['end']);
    221243
    222244        $totalOffsetSeconds = (int) $wpdb->get_var('SELECT TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), NOW())');
     
    257279        ];
    258280
     281        // Note: $data1 and $data2 are already validated and safe
     282        // All timestamps are sanitized as integers
     283        // Table prefix comes from WordPress (safe)
     284        // $tzOffset is calculated from DB query and formatted (safe)
     285       
    259286        $sql = "
    260287            SELECT
     
    274301                    {$dtExpr} AS grouped_date
    275302                FROM {$wpdb->prefix}slim_stats
    276                 WHERE dt BETWEEN {$prevArgs['start']} AND {$prevArgs['end']}
     303                WHERE dt BETWEEN {$prevStart} AND {$prevEnd}
    277304                OR dt BETWEEN {$start} AND {$end}
    278305                GROUP BY grouped_date, period
     
    291318                END AS period
    292319            FROM {$wpdb->prefix}slim_stats
    293             WHERE CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$prevArgs['start']}) AND FROM_UNIXTIME({$prevArgs['end']})
     320            WHERE CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$prevStart}) AND FROM_UNIXTIME({$prevEnd})
    294321            OR CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$start}) AND FROM_UNIXTIME({$end})
    295322            GROUP BY period
     
    302329            'params'    => ['label' => $periods[$gran]['label'], 'gran' => $gran],
    303330        ];
     331    }
     332
     333    /**
     334     * Validates SQL expressions to prevent SQL injection attacks.
     335     * Uses a predefined metrics system for maximum security.
     336     *
     337     * @param string $expression The SQL expression to validate
     338     * @return string The safe SQL expression
     339     * @throws \Exception If the expression is invalid or potentially malicious
     340     */
     341    private function validateSqlExpression(string $expression): string
     342    {
     343        global $wpdb;
     344       
     345        // Remove extra whitespace and normalize
     346        $expression = preg_replace('/\s+/', ' ', trim($expression));
     347       
     348        // Empty expressions default to COUNT(*)
     349        if (empty($expression)) {
     350            return 'COUNT(*)';
     351        }
     352       
     353        // Define allowed columns from wp_slim_stats table
     354        $allowedColumns = [
     355            'id', 'ip', 'other_ip', 'username', 'email',
     356            'country', 'location', 'city',
     357            'referer', 'resource', 'searchterms', 'notes', 'visit_id',
     358            'server_latency', 'page_performance',
     359            'browser', 'browser_version', 'browser_type', 'platform',
     360            'language', 'fingerprint', 'user_agent',
     361            'resolution', 'screen_width', 'screen_height',
     362            'content_type', 'category', 'author', 'content_id',
     363            'outbound_resource',
     364            'tz_offset', 'dt_out', 'dt'
     365        ];
     366       
     367        // Define allowed aggregate functions
     368        $allowedFunctions = ['COUNT', 'SUM', 'AVG', 'MAX', 'MIN'];
     369       
     370        // Strict pattern matching with anchors to prevent bypass attempts
     371        // Pattern 1: COUNT(*) or SUM(*) etc (no spaces allowed in function name)
     372        if (preg_match('/^(COUNT|SUM|AVG|MAX|MIN)\s*\(\s*\*\s*\)$/i', $expression, $matches)) {
     373            $function = strtoupper($matches[1]);
     374            return $function . '(*)';
     375        }
     376       
     377        // Pattern 2: COUNT(column) or COUNT( column )
     378        if (preg_match('/^(COUNT|SUM|AVG|MAX|MIN)\s*\(\s*([a-z_][a-z0-9_]*)\s*\)$/i', $expression, $matches)) {
     379            $function = strtoupper($matches[1]);
     380            $column = strtolower($matches[2]);
     381           
     382            if (!in_array($function, $allowedFunctions, true)) {
     383                throw new \Exception(__('Invalid SQL function in chart data expression', 'wp-slimstat'));
     384            }
     385           
     386            if (!in_array($column, $allowedColumns, true)) {
     387                throw new \Exception(__('Invalid column name in chart data expression', 'wp-slimstat'));
     388            }
     389           
     390            // Use esc_sql as additional protection (though column is whitelisted)
     391            return $function . '( ' . esc_sql($column) . ' )';
     392        }
     393       
     394        // Pattern 3: COUNT(DISTINCT column) or COUNT( DISTINCT column )
     395        if (preg_match('/^(COUNT|SUM|AVG|MAX|MIN)\s*\(\s*DISTINCT\s+([a-z_][a-z0-9_]*)\s*\)$/i', $expression, $matches)) {
     396            $function = strtoupper($matches[1]);
     397            $column = strtolower($matches[2]);
     398           
     399            if (!in_array($function, $allowedFunctions, true)) {
     400                throw new \Exception(__('Invalid SQL function in chart data expression', 'wp-slimstat'));
     401            }
     402           
     403            if (!in_array($column, $allowedColumns, true)) {
     404                throw new \Exception(__('Invalid column name in chart data expression', 'wp-slimstat'));
     405            }
     406           
     407            // Use esc_sql as additional protection (though column is whitelisted)
     408            return $function . '( DISTINCT ' . esc_sql($column) . ' )';
     409        }
     410       
     411        // If none of the patterns match, reject the expression
     412        throw new \Exception(__('Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed.', 'wp-slimstat'));
    304413    }
    305414
  • wp-slimstat/tags/5.3.2/wp-slimstat.php

    r3358591 r3401545  
    44 * Plugin URI: https://wp-slimstat.com/
    55 * Description: The leading web analytics plugin for WordPress
    6  * Version: 5.3.1
     6 * Version: 5.3.2
    77 * Author: Jason Crouse, VeronaLabs
    88 * Text Domain: wp-slimstat
     
    2525
    2626// Set the plugin version and directory
    27 define('SLIMSTAT_ANALYTICS_VERSION', '5.3.1');
     27define('SLIMSTAT_ANALYTICS_VERSION', '5.3.2');
    2828define('SLIMSTAT_FILE', __FILE__);
    2929define('SLIMSTAT_DIR', __DIR__);
  • wp-slimstat/trunk/CHANGELOG.md

    r3358591 r3401545  
     1= 5.3.2 - 2025-11-24 =
     2- Fix: Minor improvements & Hardened plugin security.
     3
    14= 5.3.1 - 2025-09-09 =
    25- **Fix**: Resolved "Invalid Date, NaN" error in monthly charts for 12-month ranges.
  • wp-slimstat/trunk/languages/wp-slimstat.pot

    r3358591 r3401545  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: SlimStat Analytics 5.3.1\n"
     5"Project-Id-Version: SlimStat Analytics 5.3.2\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-slimstat\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2025-09-09T12:28:59+00:00\n"
     12"POT-Creation-Date: 2025-11-24T05:39:19+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    14 "X-Generator: WP-CLI 2.11.0\n"
     14"X-Generator: WP-CLI 2.12.0\n"
    1515"X-Domain: wp-slimstat\n"
    1616
     
    306306
    307307#: admin/config/index.php:187
     308#, php-format
    308309msgid "You are currently using version %s."
    309310msgstr ""
     
    971972
    972973#: admin/index.php:901
     974#, php-format
    973975msgid "Pageviews in the last %s days"
    974976msgstr ""
    975977
    976978#: admin/index.php:903
     979#, php-format
    977980msgid "Unique IPs in the last %s days"
    978981msgstr ""
     
    12631266
    12641267#: admin/view/addons.php:23
     1268#, php-format
    12651269msgid "There was an error retrieving the add-ons list from the server. Please try again later. Error Message: %s"
    12661270msgstr ""
     
    12791283
    12801284#: admin/view/addons.php:42
     1285#, php-format
    12811286msgid "This list is refreshed once daily: <a href=\"%s&amp;force_refresh=true\" class=\"noslimstat\">click here</a> to clear the cache."
    12821287msgstr ""
     
    13211326#: admin/view/index.php:66
    13221327#: admin/view/wp-slimstat-db.php:812
    1323 #: src/Modules/Chart.php:101
     1328#: src/Modules/Chart.php:115
    13241329msgid "Today"
    13251330msgstr ""
     
    14111416#: admin/view/index.php:138
    14121417#: admin/view/wp-slimstat-reports.php:1724
     1418#, php-format
    14131419msgid "GeoIP collection is not enabled. Please go to <a href='%s' class='noslimstat'>setting page</a> to enable GeoIP for getting more information and location (country) from the visitor."
    14141420msgstr ""
    14151421
    14161422#: admin/view/index.php:142
     1423#, php-format
    14171424msgid "Install our <a href='%s' class='noslimstat'>Browscap Library</a> to identify your visitors' browser and operating system."
    14181425msgstr ""
    14191426
    14201427#: admin/view/index.php:147
     1428#, php-format
    14211429msgid "A caching plugin might be enabled on your website. Please <a href='%s' target='_blank' class='noslimstat'>make sure to configure</a> Slimstat Analytics accordingly, to get accurate information."
    14221430msgstr ""
     
    24842492
    24852493#: admin/view/wp-slimstat-reports.php:1015
     2494#, php-format
    24862495msgid "Showing %s - %s of %s"
    24872496msgstr ""
     
    47834792
    47844793#: src/Exception/LogException.php:20
     4794#, php-format
    47854795msgid "Exception occurred: [Code %d] %s at %s:%d"
    47864796msgstr ""
    47874797
    4788 #: src/Modules/Chart.php:48
     4798#: src/Modules/Chart.php:47
     4799msgid "Insufficient permissions"
     4800msgstr ""
     4801
     4802#: src/Modules/Chart.php:54
    47894803msgid "Invalid granularity"
    47904804msgstr ""
    47914805
    4792 #: src/Modules/Chart.php:99
     4806#: src/Modules/Chart.php:113
    47934807msgid "-- Previous Period"
    47944808msgstr ""
    47954809
    4796 #: src/Modules/Chart.php:100
     4810#: src/Modules/Chart.php:114
    47974811msgid "Click Tap “Previous Period” to hide or show the previous period line."
    47984812msgstr ""
    47994813
    4800 #: src/Modules/Chart.php:102
     4814#: src/Modules/Chart.php:116
    48014815msgid "30 Days ago"
    48024816msgstr ""
    48034817
    4804 #: src/Modules/Chart.php:103
     4818#: src/Modules/Chart.php:117
    48054819msgid "Day ago"
    48064820msgstr ""
    48074821
    4808 #: src/Modules/Chart.php:104
     4822#: src/Modules/Chart.php:118
    48094823msgid "Year ago"
    48104824msgstr ""
    48114825
    4812 #: src/Modules/Chart.php:105
     4826#: src/Modules/Chart.php:119
    48134827msgid "Now"
     4828msgstr ""
     4829
     4830#: src/Modules/Chart.php:383
     4831#: src/Modules/Chart.php:400
     4832msgid "Invalid SQL function in chart data expression"
     4833msgstr ""
     4834
     4835#: src/Modules/Chart.php:387
     4836#: src/Modules/Chart.php:404
     4837msgid "Invalid column name in chart data expression"
     4838msgstr ""
     4839
     4840#: src/Modules/Chart.php:412
     4841msgid "Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed."
    48144842msgstr ""
    48154843
     
    48594887
    48604888#: src/Services/GeoIP.php:178
     4889#, php-format
    48614890msgid "Error Creating GeoIP Database Directory. Ensure Web Server Has Directory Creation Permissions in: %s"
    48624891msgstr ""
    48634892
    48644893#: src/Services/GeoIP.php:182
     4894#, php-format
    48654895msgid "Error Setting Permissions for GeoIP Database Directory. Check Write Permissions for Directories in: %s"
    48664896msgstr ""
    48674897
    48684898#: src/Services/GeoIP.php:190
     4899#, php-format
    48694900msgid "Error Downloading GeoIP Database from: %1$s - %2$s"
    48704901msgstr ""
     
    48754906
    48764907#: src/Services/GeoIP.php:223
     4908#, php-format
    48774909msgid "Error Opening Downloaded GeoIP Database for Reading: %s"
    48784910msgstr ""
    48794911
    48804912#: src/Services/GeoIP.php:227
     4913#, php-format
    48814914msgid "Error Opening Destination GeoIP Database for Writing: %s"
    48824915msgstr ""
     
    48874920
    48884921#: src/Services/GeoIP.php:248
     4922#, php-format
    48894923msgid "Error: %1$s"
    48904924msgstr ""
     
    49354969
    49364970#: wp-slimstat.php:581
     4971#, php-format
    49374972msgid "Attempted XSS Injection: %s"
    49384973msgstr ""
     
    49715006
    49725007#: wp-slimstat.php:1144
     5008#, php-format
    49735009msgid "This parameter is used to filter a given dimension (resources, browsers, operating systems, etc) so that it satisfies certain conditions (i.e.: browser contains Chrome). Please make sure to urlencode this value, and to use the usual filter format: browser contains Chrome&&&referer contains slim (encoded: browser%20contains%20Chrome%26%26%26referer%20contains%20slim)"
    49745010msgstr ""
  • wp-slimstat/trunk/readme.txt

    r3358591 r3401545  
    66Requires PHP: 7.4
    77Tested up to: 6.8
    8 Stable tag: 5.3.1
     8Stable tag: 5.3.2
    99License: GPL-2.0+
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    7474
    7575== Changelog ==
     76= 5.3.2 - 2025-11-24 =
     77- Fix: Minor improvements & Hardened plugin security.
     78
    7679= 5.3.1 - 2025-09-09 =
    7780- **Fix**: Resolved "Invalid Date, NaN" error in monthly charts for 12-month ranges.
  • wp-slimstat/trunk/src/Modules/Chart.php

    r3349531 r3401545  
    4242        check_ajax_referer('slimstat_chart_nonce', 'nonce');
    4343
     44        // Additional capability check - users must be able to view stats
     45        $minimum_capability = 'read';
     46        if (!current_user_can($minimum_capability)) {
     47            wp_send_json_error(['message' => __('Insufficient permissions', 'wp-slimstat')]);
     48        }
     49
    4450        $args        = isset($_POST['args']) ? json_decode(stripslashes($_POST['args']), true) : [];
    4551        $granularity = isset($_POST['granularity']) ? sanitize_text_field($_POST['granularity']) : 'daily';
     
    4753        if (!in_array($granularity, ['yearly', 'monthly', 'weekly', 'daily', 'hourly'], true)) {
    4854            wp_send_json_error(['message' => __('Invalid granularity', 'wp-slimstat')]);
     55        }
     56       
     57        // Validate and sanitize start/end timestamps
     58        if (isset($args['start'])) {
     59            $args['start'] = absint($args['start']);
     60        }
     61        if (isset($args['end'])) {
     62            $args['end'] = absint($args['end']);
    4963        }
    5064
     
    8094                'translations' => $chart->translations,
    8195            ]);
    82         } catch (Exception $exception) {
     96        } catch (\Exception $exception) {
    8397            wp_send_json_error(['message' => $exception->getMessage()]);
    8498        }
     
    217231        $data1 = $args['chart_data']['data1'] ?? '';
    218232        $data2 = $args['chart_data']['data2'] ?? '';
    219         $start = $args['start'];
    220         $end   = $args['end'];
     233       
     234        // Validate SQL expressions to prevent SQL injection
     235        $data1 = $this->validateSqlExpression($data1);
     236        $data2 = $this->validateSqlExpression($data2);
     237       
     238        // Ensure timestamps are integers (defense in depth)
     239        $start = absint($args['start']);
     240        $end   = absint($args['end']);
     241        $prevStart = absint($prevArgs['start']);
     242        $prevEnd = absint($prevArgs['end']);
    221243
    222244        $totalOffsetSeconds = (int) $wpdb->get_var('SELECT TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), NOW())');
     
    257279        ];
    258280
     281        // Note: $data1 and $data2 are already validated and safe
     282        // All timestamps are sanitized as integers
     283        // Table prefix comes from WordPress (safe)
     284        // $tzOffset is calculated from DB query and formatted (safe)
     285       
    259286        $sql = "
    260287            SELECT
     
    274301                    {$dtExpr} AS grouped_date
    275302                FROM {$wpdb->prefix}slim_stats
    276                 WHERE dt BETWEEN {$prevArgs['start']} AND {$prevArgs['end']}
     303                WHERE dt BETWEEN {$prevStart} AND {$prevEnd}
    277304                OR dt BETWEEN {$start} AND {$end}
    278305                GROUP BY grouped_date, period
     
    291318                END AS period
    292319            FROM {$wpdb->prefix}slim_stats
    293             WHERE CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$prevArgs['start']}) AND FROM_UNIXTIME({$prevArgs['end']})
     320            WHERE CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$prevStart}) AND FROM_UNIXTIME({$prevEnd})
    294321            OR CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$start}) AND FROM_UNIXTIME({$end})
    295322            GROUP BY period
     
    302329            'params'    => ['label' => $periods[$gran]['label'], 'gran' => $gran],
    303330        ];
     331    }
     332
     333    /**
     334     * Validates SQL expressions to prevent SQL injection attacks.
     335     * Uses a predefined metrics system for maximum security.
     336     *
     337     * @param string $expression The SQL expression to validate
     338     * @return string The safe SQL expression
     339     * @throws \Exception If the expression is invalid or potentially malicious
     340     */
     341    private function validateSqlExpression(string $expression): string
     342    {
     343        global $wpdb;
     344       
     345        // Remove extra whitespace and normalize
     346        $expression = preg_replace('/\s+/', ' ', trim($expression));
     347       
     348        // Empty expressions default to COUNT(*)
     349        if (empty($expression)) {
     350            return 'COUNT(*)';
     351        }
     352       
     353        // Define allowed columns from wp_slim_stats table
     354        $allowedColumns = [
     355            'id', 'ip', 'other_ip', 'username', 'email',
     356            'country', 'location', 'city',
     357            'referer', 'resource', 'searchterms', 'notes', 'visit_id',
     358            'server_latency', 'page_performance',
     359            'browser', 'browser_version', 'browser_type', 'platform',
     360            'language', 'fingerprint', 'user_agent',
     361            'resolution', 'screen_width', 'screen_height',
     362            'content_type', 'category', 'author', 'content_id',
     363            'outbound_resource',
     364            'tz_offset', 'dt_out', 'dt'
     365        ];
     366       
     367        // Define allowed aggregate functions
     368        $allowedFunctions = ['COUNT', 'SUM', 'AVG', 'MAX', 'MIN'];
     369       
     370        // Strict pattern matching with anchors to prevent bypass attempts
     371        // Pattern 1: COUNT(*) or SUM(*) etc (no spaces allowed in function name)
     372        if (preg_match('/^(COUNT|SUM|AVG|MAX|MIN)\s*\(\s*\*\s*\)$/i', $expression, $matches)) {
     373            $function = strtoupper($matches[1]);
     374            return $function . '(*)';
     375        }
     376       
     377        // Pattern 2: COUNT(column) or COUNT( column )
     378        if (preg_match('/^(COUNT|SUM|AVG|MAX|MIN)\s*\(\s*([a-z_][a-z0-9_]*)\s*\)$/i', $expression, $matches)) {
     379            $function = strtoupper($matches[1]);
     380            $column = strtolower($matches[2]);
     381           
     382            if (!in_array($function, $allowedFunctions, true)) {
     383                throw new \Exception(__('Invalid SQL function in chart data expression', 'wp-slimstat'));
     384            }
     385           
     386            if (!in_array($column, $allowedColumns, true)) {
     387                throw new \Exception(__('Invalid column name in chart data expression', 'wp-slimstat'));
     388            }
     389           
     390            // Use esc_sql as additional protection (though column is whitelisted)
     391            return $function . '( ' . esc_sql($column) . ' )';
     392        }
     393       
     394        // Pattern 3: COUNT(DISTINCT column) or COUNT( DISTINCT column )
     395        if (preg_match('/^(COUNT|SUM|AVG|MAX|MIN)\s*\(\s*DISTINCT\s+([a-z_][a-z0-9_]*)\s*\)$/i', $expression, $matches)) {
     396            $function = strtoupper($matches[1]);
     397            $column = strtolower($matches[2]);
     398           
     399            if (!in_array($function, $allowedFunctions, true)) {
     400                throw new \Exception(__('Invalid SQL function in chart data expression', 'wp-slimstat'));
     401            }
     402           
     403            if (!in_array($column, $allowedColumns, true)) {
     404                throw new \Exception(__('Invalid column name in chart data expression', 'wp-slimstat'));
     405            }
     406           
     407            // Use esc_sql as additional protection (though column is whitelisted)
     408            return $function . '( DISTINCT ' . esc_sql($column) . ' )';
     409        }
     410       
     411        // If none of the patterns match, reject the expression
     412        throw new \Exception(__('Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed.', 'wp-slimstat'));
    304413    }
    305414
  • wp-slimstat/trunk/wp-slimstat.php

    r3358591 r3401545  
    44 * Plugin URI: https://wp-slimstat.com/
    55 * Description: The leading web analytics plugin for WordPress
    6  * Version: 5.3.1
     6 * Version: 5.3.2
    77 * Author: Jason Crouse, VeronaLabs
    88 * Text Domain: wp-slimstat
     
    2525
    2626// Set the plugin version and directory
    27 define('SLIMSTAT_ANALYTICS_VERSION', '5.3.1');
     27define('SLIMSTAT_ANALYTICS_VERSION', '5.3.2');
    2828define('SLIMSTAT_FILE', __FILE__);
    2929define('SLIMSTAT_DIR', __DIR__);
Note: See TracChangeset for help on using the changeset viewer.