WP Engine Failed to Vet Security of Plugin Acquired This Year or Fix Vulnerability in It Once It Was Reported to Them
When it comes to whether Matt Mullenweg or WP Engine are the bad guys in the recent, the reality is that they both have played a decidedly harmful role in the security of WordPress plugins. Sometimes that comes from them working together. Last year, we noted that WP Engine was falsely claiming that a popular WordPress plugin contained a security vulnerabilities. That was caused by them using a known unreliable source of vulnerabilities, WPScan. Incredibly, WP Engine’s VP of security admitted earlier in the year they haven’t done due diligence with WPScan’s data:
We know that there are other options out there, but given the sense of completeness and alerts for ALL relevant plugins, we never had a need to go crosscheck WPScan against anyone else.
WPScan is owned by Automattic, which is the company that Matt Mullenweg is the CEO of.
Earlier this year we noted that WP Engine had failed to actually fix a vulnerability in one of their plugins, Genesis Blocks. WPScan, in line with not being a reliable source, told people that the vulnerability had been fixed.
WP Engine’s lack of concern for security has continued since then. In July, they acquired the maker of the 100,000+ install plugin NitroPack. They clearly still haven’t done even basic security vetting of the plugin, as we found last month. The issue we identified hasn’t been fixed despite WP Engine having had over a month to address it, as we notified them of the issue on October 11. We gave them to 30 days to address it before we would disclose it. The developers have added new features and made other changes to the plugin in that time, so they have had developer resources available that could have fixed it. Here are the changelog entries for the changes made since we notified the developer about the issue:
1.17.3
* Improvement: Remove WooCommerce features for PSB users
* Improvement: WCML compatibility – use cookies
* Bug fix: Overall stability and fixes1.17.2
* New Feature: Display NitroPack settings for Page Speed Boost users
* New Feature: Option to allow editors to purge cache
* Improvement: Move config file to default location for WP Engine clients
* Bug fix: Overall stability and fixes
Lack of Capability Check
If you were going to do basic security vetting of a WordPress plugin, one of the things you would do is to check to make sure AJAX accessible functions have been properly secured. This is one of the quick checks we do on a regular basis when there is an indication that a hacker is targeting a plugin, because improperly secured AJAX accessible functions are common source of serious vulnerabilities. That wasn’t done by WP Engine.
As an example of checking on that, let’s look a AJAX accessible function to disconnect from NitroPack’s service.
In the file main.php, the function is registered to accessible by anyone logged in to WordPress:
120 | add_action('wp_ajax_nitropack_disconnect', 'nitropack_disconnect'); |
That registration only occurs if the function is_admin() is true:
111 | if (is_admin()) { |
That function tells you if an admin page is being accessed. Based on what is run there, it appears the developer was probably using that as intended, though they have also been confused and thought it told if the request is coming from an Administrator. The poorly named function was warned about years ago, but still hasn’t been addressed, despite repeated exploited vulnerabilities caused in part by that. (It is one of several unfixed security issues in WordPress.) That is something that Matt Mullenweg has the power to fix, but he hasn’t.
The function is located in the file functions.php. In the function, there should be a capability check before the disconnection happens. Here is the code at the beginning of the function:
2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 | function nitropack_disconnect() { nitropack_verify_ajax_nonce($_REQUEST); nitropack_uninstall_advanced_cache(); try { nitropack_event("disconnect"); if (null !== $nitro = get_nitropack_sdk()) { nitropack_reset_webhooks($nitro); } } catch (\Exception $e) { nitropack_json_and_exit(array("status" => "error", "message" => $e->getMessage())); } |
There isn’t a capability check there. Based on the name, the function nitropack_verify_ajax_nonce() would probably be doing another needed security check to prevent cross-site request forgery (CSRF). Sometimes developers include a capability check in a function like that. That function, which is in the same file, doesn’t include a capabilities check:
2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 | function nitropack_verify_ajax_nonce($request_data) { // If not an ajax request if (!defined('DOING_AJAX') || !DOING_AJAX) { return; } // If nonce fails verification if (empty($request_data['nonce']) || !wp_verify_nonce($request_data['nonce'], NITROPACK_NONCE)) { wp_die('Unauthorized request'); } } |
The nonce check to prevent CSRF there would normally limit who has access, though WordPress’ documentation warns against relying on that:
Nonces should never be relied on for authentication, authorization, or access control. Protect your functions using
current_user_can()
, and always assume nonces can be compromised.
This plugin is a good example of why there is that warning. It turns out the nonce value needed to pass the check is included on every admin page of WordPress.
That occurs in the following code:
1250 | add_action('admin_enqueue_scripts', 'load_nitropack_scripts_styles'); |
1216 1217 1218 1219 1220 1221 1222 | function load_nitropack_scripts_styles($page) { //global WP wp_enqueue_style('nitropack-notifications', plugin_dir_url(__FILE__) . 'view/stylesheet/nitro-notifications.min.css', array(), NITROPACK_VERSION); wp_enqueue_script('nitropack_notices_js', plugin_dir_url(__FILE__) . 'view/javascript/np_notices.js', array(), NITROPACK_VERSION, true); wp_localize_script('nitropack_notices_js', 'nitropack_notices_vars', array( 'nonce' => wp_create_nonce(NITROPACK_NONCE), )); |
So anyone logged in to WordPress is able to do the disconnection.
The problem isn’t limited to that function, there are numerous other AJAX accessible functions that lack the capability check. We didn’t look to see what all could be an attacker could accomplish with that.
Reviews Don’t Cost That Much
Even if WP Engine doesn’t have the in house capability to review the security of its plugins, it wouldn’t break the bank to have hired someone to do a review of the plugin. Our price for a review would be $500, which is less than it is cost than for one hour of the time of their lawyers in their current legal case with Matt Mullenweg and Automattic.
Long Time Issue
The issue has existed since the first release of the plugin in the WordPress plugin directory, back in August 2019. It was only in November of last year that the nonce check was added. For some reason WPScan and Wordfence claimed the capability check was added in when the nonce check was actually added.
Timeline
- October 11 – NitroPack notified through security email address listed on their contact page.
- October 17 – WP Engine’s VP of security responds that we “are working with the NitroPack engineers to resolve it soon.”
Proof of Concept
The following proof of concept will disconnect from NitroPack’s service, when logged in to WordPress.
Make sure to replace “[path to WordPress]” with the location of WordPress and “[nonce]” with the value of “nonce” in the JavaScript variable var nitropack_notices_vars in the source code of admin pages of the website.
http://[path to WordPress]/wp-admin/admin-ajax.php?action=nitropack_disconnect&nonce=[nonce]