WordPress VeronaLabs WP Statistics plugin versions 13.1.4 and suffer from a remote unauthenticated blind SQL injection vulnerability.
a44a0f725e12fffd6ea14867d86b60a1eee8bfb6e7da84649c917358d8aa5478
On February 7, 2022, Security Researcher Cyku Hong from DEVCORE reported a vulnerability to us that they discovered in WP Statistics, a WordPress plugin installed on over 600,000 sites. This vulnerability made it possible for unauthenticated attackers to execute arbitrary SQL queries by appending them to an existing SQL query. This could be used to extract sensitive information like password hashes and secret keys from the database. On request, we assigned them the vulnerability identifier: CVE-2022-0513.
Even though Wordfence provides protection against this vulnerability, we strongly recommend ensuring that your site has been updated to the latest patched version of “WP Statistics,” which is version 13.1.5 at the time of this publication.
Description: Unauthenticated Blind SQL Injection
Affected Plugin: WP Statistics
Plugin Slug: wp-statistics
Plugin Developer: VeronaLabs
Affected Versions: <= 13.1.4
CVE ID: CVE-2022-0513
CVSS Score: 9.8 (Critical)
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Researcher/s: Cyku Hong from DEVCORE
Fully Patched Version: 13.1.5
WP Statistics is a WordPress plugin designed to provide a centralized hub for all of a WordPress site’s statistics, such as visitor data, and it emphasizes storing this data locally to the WordPress site to preserve user privacy. As such, it is reasonable to expect that the plugin would implement a lot of functionality to store and retrieve information from the database through the use of SQL queries. Unfortunately, the implementation of one of these queries was insecure, creating a SQL Injection vulnerability.
When the “Record Exclusions'' feature was enabled, this vulnerability became exploitable. The “Record Exclusions” feature is designed to record when a visit, or a “hit”, is excluded from the site’s statistics, such as visits by users with specific roles, login page access, and anything else that a site owner may have explicitly selected to exclude. It records that data to a separate database table so as not to contaminate the main statistical data the plugin collects.
In order to record these hits when a caching plugin was enabled, the plugin registered a REST route /wp-json/wp-statistics/v2/hit that would call the hit_callback() function. This function would then call the record() function from the ‘Hits’ class which checks to see if the request should be excluded and determines what exclusion the request correlates to, prior to calling the next appropriate record() function.
public static function record()
{
# Check Exclusion This Hits
$exclusion = Exclusion::check();
# Record Hits Exclusion
if ($exclusion['exclusion_match'] === true) {
Exclusion::record($exclusion);
}
# Record User Visits
if (Visit::active() and $exclusion['exclusion_match'] === false) {
Visit::record();
}
# Record Visitor Detail
if (Visitor::active()) {
$visitor_id = Visitor::record($exclusion);
}
When the exclusion_match parameter is set to true in a request, the data is then passed to the record() function from the ‘Exclusion’ class where the plugin attempts to update the count of an exclusion reason for the day if it is present in the database. If the exclusion reason isn’t present in the database for the current date the initial query will return false and trigger the next query to add a new record count to the table for the reason.
public static function record($exclusion = array())
{
global $wpdb;
// If we're not storing exclusions, just return.
if (self::record_active() != true) {
return;
}
// Check Exist this Exclusion in this day
$result = $wpdb->query("UPDATE " . DB::table('exclusions') . " SET `count` = `count` + 1 WHERE `date` = '" . TimeZone::getCurrentDate('Y-m-d') . "' AND `reason` = '{$exclusion['exclusion_reason']}'");
if (!$result) {
$insert = $wpdb->insert(
DB::table('exclusions'),
array(
'date' => TimeZone::getCurrentDate('Y-m-d'),
'reason' => $exclusion['exclusion_reason'],
'count' => 1,
)
);
if (!$insert) {
if (!empty($wpdb->last_error)) {
\WP_Statistics::log($wpdb->last_error);
}
}
The $wpdb->query() function was used for the initial UPDATE query and used the user-supplied 'exclusion_reason' value as part of the query. Due to the fact that there was no escaping on the user supplied value, or parameterization on the query, attackers could easily append additional SQL queries to the existing query via the 'exclusion_reason' and extract sensitive information from the database.
Since no data from the SQL query was returned in the response, and the response did not indicate a boolean answer, an attacker would need to use a Time-Based blind approach to extract information from the database. This means that they would need to use SQL CASE statements along with the SLEEP() command while observing the response time of each request to steal information from the database. This is an intricate, yet frequently successful method to obtain information from a database when exploiting SQL Injection vulnerabilities.
Upon further analysis, we uncovered that a user could also simply pass the exclusion_match parameter equal to yes, the exclusion_reason parameter set to the SQLi payload, and the wp_statistics_hit_rest parameter set to true, along with passing the string wp-json/ in the request URI to trigger the same record() function from the ‘Exclusions’ class. This method did not require a caching plugin to be enabled to obtain a valid nonce to trigger the REST endpoint. This is due to the is_rest_request() function returning true when the $_SERVER['REQUEST_URI'] contains the REST prefix, wp-json/, even if the request isn’t a genuine REST request. This ultimately triggers the entire record process.
Conclusion
In today’s post, we detailed a flaw in the “WP Statistics” plugin that made it possible for unauthenticated attackers to inject arbitrary SQL queries to steal sensitive information from a database. This flaw has been fully patched in version 13.1.5.
We recommend that WordPress site owners immediately verify that their site has been updated to the latest patched version available, which is version 13.1.5 at the time of this publication.
All Wordfence users, including Free are protected from exploits targeting this vulnerability thanks to the Wordfence Firewall’s built-in SQL Injection protection.
If you know a friend or colleague who is using this plugin on their site, we highly recommend forwarding this advisory to them to help keep their sites protected as this is a serious vulnerability that can lead to complete site takeover.
Congratulations to Cyku Hong from DEVCORE for discovering and responsibly disclosing this vulnerability to the plugin’s developers.