Hacker Probing For WordPress Plugin With Many Vulnerabilities That Wordfence and Other Providers Incorrectly Claimed Were Fixed Last Year
Today we saw what appeared to be a hacker probing for usage of the WordPress plugin WP Compress on our websites. The probing was done by requesting a file from the plugin if the plugin had existed on our website, /wp-content/plugins/wp-compress-image-optimizer/readme.txt. We don’t use that plugin on that website or any of them. So what might explain a hacker’s interest in the plugin? Last year the WordPress security provider Wordfence claimed that a vulnerability had been fixed in the plugin, of a type that sounds like it could explain a hacker’s interest. Here is part of their description:
This makes it possible for authenticated attackers, with subscriber-level permissions and above, to edit plugin settings, including storing cross-site scripting, in multisite environments.
The last part of that is odd. Why would that only be possible in “multisite environments?” They didn’t provide any further details to explain. They provided two links without any context as to what they were supposed to mean. The links showed a couple of things. First Wordfence had failed to vet the fix, as even the code being changed was still vulnerable. One of the links shows code being changed to add a capability check, notably missing is the addition of a nonce check or a pre-existing nonce checks. So there was still at least cross-site request forgery (CSRF) vulnerability. A little more checking showed that the capability check was incompletely applied to file being changed.
In the file /classes/mu.class.php, the following function had a capability check added (the current_user_can() part):
553 554 555 556 557 558 559 | public function mu_save_site_settings() { if (!current_user_can('manage_options')) { wp_send_json_error('Forbidden.'); } global $wpc_siteID; |
The next function should have also had that added, but it wasn’t:
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 | public function mu_get_site_settings() { global $wpc_siteID; $output = ''; $wpc_siteID = $siteID = sanitize_title($_POST['siteID']); ob_start(); // begin collecting output if ($this->mu_is_connected($siteID)) { include WPS_IC_DIR . 'templates/mu/connected.php'; #include WPS_IC_DIR . 'templates/mu/site-settings.php'; } else { include WPS_IC_DIR . 'templates/mu/not-connected.php'; } $output .= ob_get_clean(); // retrieve output from myfile.php, stop buffering wp_send_json_success($output); } |
So even as described by Wordfence, the issue had only been partially fixed. It turns out the issue is still exploitable even when not using WordPress Multisite.
Plugin Vulnerability Data Providers Don’t Vet Their Data
That Wordfence got things so wrong runs counter to their claim to have a team of experts and their CEO Mark Maunder’s claim that their vulnerability “data is impeccable.”
They were not alone in that. WPScan, which also claims to have a team of experts, claimed the issue was fixed:
While also somehow claiming that they hadn’t verified it (somehow that is a miscellaneous detail):
Patchstack, also claims to have a team of experts, yet they also claimed it was fixed:
They are claiming to have provided a “virtual patch”, so either they knew the fix was incomplete and claimed otherwise, or their virtual patch is incomplete as well.
The reality is that WPScan and Patchstack simply copy information from Wordfence without vetting, despite Wordfence’s data being known to be unreliable going back to before it was public. (Wordfence also copies data from the other providers without vetting it.)
So Much is Vulnerable
When trying to determine what a hacker might be interested in exploiting related to this, you run into the problem that is so much code that is accessible it is hard to guess what a hacker might be interested. But to provide an example of what Wordfence and the other security providers somehow missed, let’s take a look at a function to change the plugin’s settings. In the file /classes/ajax.class.php, the function wps_ic_settings_change() is registered to be accessible through WordPress’ AJAX functionality to anyone logged in to WordPress:
126 | $this->add_ajax('wps_ic_settings_change'); |
That function doesn’t include a capability check or a nonce check to limit access to changing the settings:
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 | /** * Change Settings Value */ public function wps_ic_settings_change() { global $wps_ic; $what = sanitize_text_field($_POST['what']); $value = sanitize_text_field($_POST['value']); $checked = sanitize_text_field($_POST['checked']); $checkbox = sanitize_text_field($_POST['checkbox']); $options = new wps_ic_options(); $settings = $options->get_settings(); if ($what == 'thumbnails') { if (!isset($value) || empty($value)) { $settings['thumbnails'] = []; } else { $settings['thumbnails'] = []; $value = rtrim($value, ','); $value = explode(',', $value); foreach ($value as $i => $thumb_size) { $settings['thumbnails'][$thumb_size] = 1; } } } else { if ($what == 'autopilot') { if ($checked == 'checked') { } else { $settings['otto'] = 'automated'; } } if ($checkbox == 'true') { if ($checked === 'false') { $settings[$what] = 0; } else { $settings[$what] = 1; } } else { $settings[$what] = $value; } } if ($what == 'live_autopilot') { if ($value == '1') { // Enabline Live, clear local queue delete_option('wps_ic_bg_stop'); delete_option('wps_ic_bg_process_stop'); delete_option('wps_ic_bg_stopping'); delete_option('wps_ic_bg_process'); delete_option('wps_ic_bg_process_done'); delete_option('wps_ic_bg_process_running'); delete_option('wps_ic_bg_process_stats'); delete_option('wps_ic_bg_last_run_compress'); delete_option('wps_ic_bg_last_run_restore'); } } elseif ($what == 'css' || $what == 'js') { // Purge CSS/JS Cache $this->purge_cdn_assets(); } self::$cacheIntegrations->purgeAll(); update_option(WPS_IC_SETTINGS, $settings); wp_send_json_success(); } |
So anyone logged in to WordPress can change the plugin’s settings. An attacker could cause anyone logged in to change settings through CSRF as well.
More Issues Probably Exists
Our Plugin Security Scorecard and Plugin Security Checker tools flag the plugin as possibly having yet more security issues.
Developer Notified
We reached out to the developer earlier today to let them known about the incomplete fix and offered to help them address it.
Free Warning
As some part of the vulnerability might be targeted by hackers, we are adding accurate data on it to the free data that comes with our Plugin Vulnerabilities plugin.
Plugin Security Scorecard Grade for Patchstack
Checked on March 5, 2025See issues causing the plugin to get less than A+ grade