Changeset 3401545
- Timestamp:
- 11/24/2025 05:41:38 AM (4 months ago)
- Location:
- wp-slimstat
- Files:
-
- 10 edited
- 1 copied
-
tags/5.3.2 (copied) (copied from wp-slimstat/trunk)
-
tags/5.3.2/CHANGELOG.md (modified) (1 diff)
-
tags/5.3.2/languages/wp-slimstat.pot (modified) (15 diffs)
-
tags/5.3.2/readme.txt (modified) (2 diffs)
-
tags/5.3.2/src/Modules/Chart.php (modified) (8 diffs)
-
tags/5.3.2/wp-slimstat.php (modified) (2 diffs)
-
trunk/CHANGELOG.md (modified) (1 diff)
-
trunk/languages/wp-slimstat.pot (modified) (15 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/src/Modules/Chart.php (modified) (8 diffs)
-
trunk/wp-slimstat.php (modified) (2 diffs)
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 1 4 = 5.3.1 - 2025-09-09 = 2 5 - **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 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: SlimStat Analytics 5.3. 1\n"5 "Project-Id-Version: SlimStat Analytics 5.3.2\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-slimstat\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "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" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 "X-Generator: WP-CLI 2.1 1.0\n"14 "X-Generator: WP-CLI 2.12.0\n" 15 15 "X-Domain: wp-slimstat\n" 16 16 … … 306 306 307 307 #: admin/config/index.php:187 308 #, php-format 308 309 msgid "You are currently using version %s." 309 310 msgstr "" … … 971 972 972 973 #: admin/index.php:901 974 #, php-format 973 975 msgid "Pageviews in the last %s days" 974 976 msgstr "" 975 977 976 978 #: admin/index.php:903 979 #, php-format 977 980 msgid "Unique IPs in the last %s days" 978 981 msgstr "" … … 1263 1266 1264 1267 #: admin/view/addons.php:23 1268 #, php-format 1265 1269 msgid "There was an error retrieving the add-ons list from the server. Please try again later. Error Message: %s" 1266 1270 msgstr "" … … 1279 1283 1280 1284 #: admin/view/addons.php:42 1285 #, php-format 1281 1286 msgid "This list is refreshed once daily: <a href=\"%s&force_refresh=true\" class=\"noslimstat\">click here</a> to clear the cache." 1282 1287 msgstr "" … … 1321 1326 #: admin/view/index.php:66 1322 1327 #: admin/view/wp-slimstat-db.php:812 1323 #: src/Modules/Chart.php:1 011328 #: src/Modules/Chart.php:115 1324 1329 msgid "Today" 1325 1330 msgstr "" … … 1411 1416 #: admin/view/index.php:138 1412 1417 #: admin/view/wp-slimstat-reports.php:1724 1418 #, php-format 1413 1419 msgid "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." 1414 1420 msgstr "" 1415 1421 1416 1422 #: admin/view/index.php:142 1423 #, php-format 1417 1424 msgid "Install our <a href='%s' class='noslimstat'>Browscap Library</a> to identify your visitors' browser and operating system." 1418 1425 msgstr "" 1419 1426 1420 1427 #: admin/view/index.php:147 1428 #, php-format 1421 1429 msgid "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." 1422 1430 msgstr "" … … 2484 2492 2485 2493 #: admin/view/wp-slimstat-reports.php:1015 2494 #, php-format 2486 2495 msgid "Showing %s - %s of %s" 2487 2496 msgstr "" … … 4783 4792 4784 4793 #: src/Exception/LogException.php:20 4794 #, php-format 4785 4795 msgid "Exception occurred: [Code %d] %s at %s:%d" 4786 4796 msgstr "" 4787 4797 4788 #: src/Modules/Chart.php:48 4798 #: src/Modules/Chart.php:47 4799 msgid "Insufficient permissions" 4800 msgstr "" 4801 4802 #: src/Modules/Chart.php:54 4789 4803 msgid "Invalid granularity" 4790 4804 msgstr "" 4791 4805 4792 #: src/Modules/Chart.php: 994806 #: src/Modules/Chart.php:113 4793 4807 msgid "-- Previous Period" 4794 4808 msgstr "" 4795 4809 4796 #: src/Modules/Chart.php:1 004810 #: src/Modules/Chart.php:114 4797 4811 msgid "Click Tap “Previous Period” to hide or show the previous period line." 4798 4812 msgstr "" 4799 4813 4800 #: src/Modules/Chart.php:1 024814 #: src/Modules/Chart.php:116 4801 4815 msgid "30 Days ago" 4802 4816 msgstr "" 4803 4817 4804 #: src/Modules/Chart.php:1 034818 #: src/Modules/Chart.php:117 4805 4819 msgid "Day ago" 4806 4820 msgstr "" 4807 4821 4808 #: src/Modules/Chart.php:1 044822 #: src/Modules/Chart.php:118 4809 4823 msgid "Year ago" 4810 4824 msgstr "" 4811 4825 4812 #: src/Modules/Chart.php:1 054826 #: src/Modules/Chart.php:119 4813 4827 msgid "Now" 4828 msgstr "" 4829 4830 #: src/Modules/Chart.php:383 4831 #: src/Modules/Chart.php:400 4832 msgid "Invalid SQL function in chart data expression" 4833 msgstr "" 4834 4835 #: src/Modules/Chart.php:387 4836 #: src/Modules/Chart.php:404 4837 msgid "Invalid column name in chart data expression" 4838 msgstr "" 4839 4840 #: src/Modules/Chart.php:412 4841 msgid "Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed." 4814 4842 msgstr "" 4815 4843 … … 4859 4887 4860 4888 #: src/Services/GeoIP.php:178 4889 #, php-format 4861 4890 msgid "Error Creating GeoIP Database Directory. Ensure Web Server Has Directory Creation Permissions in: %s" 4862 4891 msgstr "" 4863 4892 4864 4893 #: src/Services/GeoIP.php:182 4894 #, php-format 4865 4895 msgid "Error Setting Permissions for GeoIP Database Directory. Check Write Permissions for Directories in: %s" 4866 4896 msgstr "" 4867 4897 4868 4898 #: src/Services/GeoIP.php:190 4899 #, php-format 4869 4900 msgid "Error Downloading GeoIP Database from: %1$s - %2$s" 4870 4901 msgstr "" … … 4875 4906 4876 4907 #: src/Services/GeoIP.php:223 4908 #, php-format 4877 4909 msgid "Error Opening Downloaded GeoIP Database for Reading: %s" 4878 4910 msgstr "" 4879 4911 4880 4912 #: src/Services/GeoIP.php:227 4913 #, php-format 4881 4914 msgid "Error Opening Destination GeoIP Database for Writing: %s" 4882 4915 msgstr "" … … 4887 4920 4888 4921 #: src/Services/GeoIP.php:248 4922 #, php-format 4889 4923 msgid "Error: %1$s" 4890 4924 msgstr "" … … 4935 4969 4936 4970 #: wp-slimstat.php:581 4971 #, php-format 4937 4972 msgid "Attempted XSS Injection: %s" 4938 4973 msgstr "" … … 4971 5006 4972 5007 #: wp-slimstat.php:1144 5008 #, php-format 4973 5009 msgid "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)" 4974 5010 msgstr "" -
wp-slimstat/tags/5.3.2/readme.txt
r3358591 r3401545 6 6 Requires PHP: 7.4 7 7 Tested up to: 6.8 8 Stable tag: 5.3. 18 Stable tag: 5.3.2 9 9 License: GPL-2.0+ 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 74 74 75 75 == Changelog == 76 = 5.3.2 - 2025-11-24 = 77 - Fix: Minor improvements & Hardened plugin security. 78 76 79 = 5.3.1 - 2025-09-09 = 77 80 - **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 42 42 check_ajax_referer('slimstat_chart_nonce', 'nonce'); 43 43 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 44 50 $args = isset($_POST['args']) ? json_decode(stripslashes($_POST['args']), true) : []; 45 51 $granularity = isset($_POST['granularity']) ? sanitize_text_field($_POST['granularity']) : 'daily'; … … 47 53 if (!in_array($granularity, ['yearly', 'monthly', 'weekly', 'daily', 'hourly'], true)) { 48 54 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']); 49 63 } 50 64 … … 80 94 'translations' => $chart->translations, 81 95 ]); 82 } catch ( Exception $exception) {96 } catch (\Exception $exception) { 83 97 wp_send_json_error(['message' => $exception->getMessage()]); 84 98 } … … 217 231 $data1 = $args['chart_data']['data1'] ?? ''; 218 232 $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']); 221 243 222 244 $totalOffsetSeconds = (int) $wpdb->get_var('SELECT TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), NOW())'); … … 257 279 ]; 258 280 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 259 286 $sql = " 260 287 SELECT … … 274 301 {$dtExpr} AS grouped_date 275 302 FROM {$wpdb->prefix}slim_stats 276 WHERE dt BETWEEN {$prev Args['start']} AND {$prevArgs['end']}303 WHERE dt BETWEEN {$prevStart} AND {$prevEnd} 277 304 OR dt BETWEEN {$start} AND {$end} 278 305 GROUP BY grouped_date, period … … 291 318 END AS period 292 319 FROM {$wpdb->prefix}slim_stats 293 WHERE CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$prev Args['start']}) AND FROM_UNIXTIME({$prevArgs['end']})320 WHERE CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$prevStart}) AND FROM_UNIXTIME({$prevEnd}) 294 321 OR CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$start}) AND FROM_UNIXTIME({$end}) 295 322 GROUP BY period … … 302 329 'params' => ['label' => $periods[$gran]['label'], 'gran' => $gran], 303 330 ]; 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')); 304 413 } 305 414 -
wp-slimstat/tags/5.3.2/wp-slimstat.php
r3358591 r3401545 4 4 * Plugin URI: https://wp-slimstat.com/ 5 5 * Description: The leading web analytics plugin for WordPress 6 * Version: 5.3. 16 * Version: 5.3.2 7 7 * Author: Jason Crouse, VeronaLabs 8 8 * Text Domain: wp-slimstat … … 25 25 26 26 // Set the plugin version and directory 27 define('SLIMSTAT_ANALYTICS_VERSION', '5.3. 1');27 define('SLIMSTAT_ANALYTICS_VERSION', '5.3.2'); 28 28 define('SLIMSTAT_FILE', __FILE__); 29 29 define('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 1 4 = 5.3.1 - 2025-09-09 = 2 5 - **Fix**: Resolved "Invalid Date, NaN" error in monthly charts for 12-month ranges. -
wp-slimstat/trunk/languages/wp-slimstat.pot
r3358591 r3401545 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: SlimStat Analytics 5.3. 1\n"5 "Project-Id-Version: SlimStat Analytics 5.3.2\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-slimstat\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "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" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 "X-Generator: WP-CLI 2.1 1.0\n"14 "X-Generator: WP-CLI 2.12.0\n" 15 15 "X-Domain: wp-slimstat\n" 16 16 … … 306 306 307 307 #: admin/config/index.php:187 308 #, php-format 308 309 msgid "You are currently using version %s." 309 310 msgstr "" … … 971 972 972 973 #: admin/index.php:901 974 #, php-format 973 975 msgid "Pageviews in the last %s days" 974 976 msgstr "" 975 977 976 978 #: admin/index.php:903 979 #, php-format 977 980 msgid "Unique IPs in the last %s days" 978 981 msgstr "" … … 1263 1266 1264 1267 #: admin/view/addons.php:23 1268 #, php-format 1265 1269 msgid "There was an error retrieving the add-ons list from the server. Please try again later. Error Message: %s" 1266 1270 msgstr "" … … 1279 1283 1280 1284 #: admin/view/addons.php:42 1285 #, php-format 1281 1286 msgid "This list is refreshed once daily: <a href=\"%s&force_refresh=true\" class=\"noslimstat\">click here</a> to clear the cache." 1282 1287 msgstr "" … … 1321 1326 #: admin/view/index.php:66 1322 1327 #: admin/view/wp-slimstat-db.php:812 1323 #: src/Modules/Chart.php:1 011328 #: src/Modules/Chart.php:115 1324 1329 msgid "Today" 1325 1330 msgstr "" … … 1411 1416 #: admin/view/index.php:138 1412 1417 #: admin/view/wp-slimstat-reports.php:1724 1418 #, php-format 1413 1419 msgid "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." 1414 1420 msgstr "" 1415 1421 1416 1422 #: admin/view/index.php:142 1423 #, php-format 1417 1424 msgid "Install our <a href='%s' class='noslimstat'>Browscap Library</a> to identify your visitors' browser and operating system." 1418 1425 msgstr "" 1419 1426 1420 1427 #: admin/view/index.php:147 1428 #, php-format 1421 1429 msgid "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." 1422 1430 msgstr "" … … 2484 2492 2485 2493 #: admin/view/wp-slimstat-reports.php:1015 2494 #, php-format 2486 2495 msgid "Showing %s - %s of %s" 2487 2496 msgstr "" … … 4783 4792 4784 4793 #: src/Exception/LogException.php:20 4794 #, php-format 4785 4795 msgid "Exception occurred: [Code %d] %s at %s:%d" 4786 4796 msgstr "" 4787 4797 4788 #: src/Modules/Chart.php:48 4798 #: src/Modules/Chart.php:47 4799 msgid "Insufficient permissions" 4800 msgstr "" 4801 4802 #: src/Modules/Chart.php:54 4789 4803 msgid "Invalid granularity" 4790 4804 msgstr "" 4791 4805 4792 #: src/Modules/Chart.php: 994806 #: src/Modules/Chart.php:113 4793 4807 msgid "-- Previous Period" 4794 4808 msgstr "" 4795 4809 4796 #: src/Modules/Chart.php:1 004810 #: src/Modules/Chart.php:114 4797 4811 msgid "Click Tap “Previous Period” to hide or show the previous period line." 4798 4812 msgstr "" 4799 4813 4800 #: src/Modules/Chart.php:1 024814 #: src/Modules/Chart.php:116 4801 4815 msgid "30 Days ago" 4802 4816 msgstr "" 4803 4817 4804 #: src/Modules/Chart.php:1 034818 #: src/Modules/Chart.php:117 4805 4819 msgid "Day ago" 4806 4820 msgstr "" 4807 4821 4808 #: src/Modules/Chart.php:1 044822 #: src/Modules/Chart.php:118 4809 4823 msgid "Year ago" 4810 4824 msgstr "" 4811 4825 4812 #: src/Modules/Chart.php:1 054826 #: src/Modules/Chart.php:119 4813 4827 msgid "Now" 4828 msgstr "" 4829 4830 #: src/Modules/Chart.php:383 4831 #: src/Modules/Chart.php:400 4832 msgid "Invalid SQL function in chart data expression" 4833 msgstr "" 4834 4835 #: src/Modules/Chart.php:387 4836 #: src/Modules/Chart.php:404 4837 msgid "Invalid column name in chart data expression" 4838 msgstr "" 4839 4840 #: src/Modules/Chart.php:412 4841 msgid "Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed." 4814 4842 msgstr "" 4815 4843 … … 4859 4887 4860 4888 #: src/Services/GeoIP.php:178 4889 #, php-format 4861 4890 msgid "Error Creating GeoIP Database Directory. Ensure Web Server Has Directory Creation Permissions in: %s" 4862 4891 msgstr "" 4863 4892 4864 4893 #: src/Services/GeoIP.php:182 4894 #, php-format 4865 4895 msgid "Error Setting Permissions for GeoIP Database Directory. Check Write Permissions for Directories in: %s" 4866 4896 msgstr "" 4867 4897 4868 4898 #: src/Services/GeoIP.php:190 4899 #, php-format 4869 4900 msgid "Error Downloading GeoIP Database from: %1$s - %2$s" 4870 4901 msgstr "" … … 4875 4906 4876 4907 #: src/Services/GeoIP.php:223 4908 #, php-format 4877 4909 msgid "Error Opening Downloaded GeoIP Database for Reading: %s" 4878 4910 msgstr "" 4879 4911 4880 4912 #: src/Services/GeoIP.php:227 4913 #, php-format 4881 4914 msgid "Error Opening Destination GeoIP Database for Writing: %s" 4882 4915 msgstr "" … … 4887 4920 4888 4921 #: src/Services/GeoIP.php:248 4922 #, php-format 4889 4923 msgid "Error: %1$s" 4890 4924 msgstr "" … … 4935 4969 4936 4970 #: wp-slimstat.php:581 4971 #, php-format 4937 4972 msgid "Attempted XSS Injection: %s" 4938 4973 msgstr "" … … 4971 5006 4972 5007 #: wp-slimstat.php:1144 5008 #, php-format 4973 5009 msgid "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)" 4974 5010 msgstr "" -
wp-slimstat/trunk/readme.txt
r3358591 r3401545 6 6 Requires PHP: 7.4 7 7 Tested up to: 6.8 8 Stable tag: 5.3. 18 Stable tag: 5.3.2 9 9 License: GPL-2.0+ 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 74 74 75 75 == Changelog == 76 = 5.3.2 - 2025-11-24 = 77 - Fix: Minor improvements & Hardened plugin security. 78 76 79 = 5.3.1 - 2025-09-09 = 77 80 - **Fix**: Resolved "Invalid Date, NaN" error in monthly charts for 12-month ranges. -
wp-slimstat/trunk/src/Modules/Chart.php
r3349531 r3401545 42 42 check_ajax_referer('slimstat_chart_nonce', 'nonce'); 43 43 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 44 50 $args = isset($_POST['args']) ? json_decode(stripslashes($_POST['args']), true) : []; 45 51 $granularity = isset($_POST['granularity']) ? sanitize_text_field($_POST['granularity']) : 'daily'; … … 47 53 if (!in_array($granularity, ['yearly', 'monthly', 'weekly', 'daily', 'hourly'], true)) { 48 54 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']); 49 63 } 50 64 … … 80 94 'translations' => $chart->translations, 81 95 ]); 82 } catch ( Exception $exception) {96 } catch (\Exception $exception) { 83 97 wp_send_json_error(['message' => $exception->getMessage()]); 84 98 } … … 217 231 $data1 = $args['chart_data']['data1'] ?? ''; 218 232 $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']); 221 243 222 244 $totalOffsetSeconds = (int) $wpdb->get_var('SELECT TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), NOW())'); … … 257 279 ]; 258 280 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 259 286 $sql = " 260 287 SELECT … … 274 301 {$dtExpr} AS grouped_date 275 302 FROM {$wpdb->prefix}slim_stats 276 WHERE dt BETWEEN {$prev Args['start']} AND {$prevArgs['end']}303 WHERE dt BETWEEN {$prevStart} AND {$prevEnd} 277 304 OR dt BETWEEN {$start} AND {$end} 278 305 GROUP BY grouped_date, period … … 291 318 END AS period 292 319 FROM {$wpdb->prefix}slim_stats 293 WHERE CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$prev Args['start']}) AND FROM_UNIXTIME({$prevArgs['end']})320 WHERE CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$prevStart}) AND FROM_UNIXTIME({$prevEnd}) 294 321 OR CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$start}) AND FROM_UNIXTIME({$end}) 295 322 GROUP BY period … … 302 329 'params' => ['label' => $periods[$gran]['label'], 'gran' => $gran], 303 330 ]; 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')); 304 413 } 305 414 -
wp-slimstat/trunk/wp-slimstat.php
r3358591 r3401545 4 4 * Plugin URI: https://wp-slimstat.com/ 5 5 * Description: The leading web analytics plugin for WordPress 6 * Version: 5.3. 16 * Version: 5.3.2 7 7 * Author: Jason Crouse, VeronaLabs 8 8 * Text Domain: wp-slimstat … … 25 25 26 26 // Set the plugin version and directory 27 define('SLIMSTAT_ANALYTICS_VERSION', '5.3. 1');27 define('SLIMSTAT_ANALYTICS_VERSION', '5.3.2'); 28 28 define('SLIMSTAT_FILE', __FILE__); 29 29 define('SLIMSTAT_DIR', __DIR__);
Note: See TracChangeset
for help on using the changeset viewer.