11 Jul

Protecting You Against Wordfence’s Bad Practices: Remote Code Execution (RCE) Vulnerability in WP Maintenance Mode

Wordfence is putting WordPress website at risk by disclosing vulnerabilities in plugins with critical details needed to double check their work missing, in what appears to be an attempt to profit off of these vulnerabilities. We are releasing those details so that others can review the vulnerabilities to try to limit the damage Wordfence’s practice could cause.

Wordfence describes the remote code execution (RCE) vulnerability in WP Maintenance Mode version 2.0.6 as “allows unsanitized user input to be evaluated as PHP code. In WordPress Multisite, a site administrator could exploit this vulnerability to execute shell commands, access sensitive information, escalate privileges or cause denial of service”.

Looking at the changes made between the version we found that this vulnerability related to the setting of Google Analytics tracking code.

One of the relevant changes is that code for saving the inputted value for the the tracking code has been changed in the file /includes/classes/wp-maintenance-mode-admin.php.

Here is the code in 2.0.6:

365
$_POST['options']['modules']['ga_code'] = wp_kses(trim($_POST['options']['modules']['ga_code']), array('script' => array()));

And the code in 2.0.7:

374
$_POST['options']['modules']['ga_code'] = wpmm_sanitize_ga_code($_POST['options']['modules']['ga_code']);

In 2.0.6 the input is run through wp_kses(), which is intended for restricting the usage of HTML elements.

The function wpmm_sanitize_ga_code() used instead in the 2.0.7 version is added in the file /includes/functions/helpers.php:

86
87
88
89
90
function wpmm_sanitize_ga_code($string) {
    preg_match('/UA-\d{4,10}(-\d{1,4})?/', $string, $matches);
 
    return isset($matches[0]) ? $matches[0] : '';
}

That severely limits what can be saved versus wp_kses().

When the input is outputted it was done this was in 2.0.6 (in the file /includes/classes/wp-maintenance-mode.php):

43
44
45
46
// Google Analytics tracking script
if (!empty($this->plugin_settings['modules']['ga_status']) && $this->plugin_settings['modules']['ga_status'] == 1 && !empty($this->plugin_settings['modules']['ga_code'])) {
	add_action('wpmm_head', create_function('', 'echo "' . stripslashes($this->plugin_settings['modules']['ga_code']) . '";'));
}

You can see that the tracking code input is now intended to be echoed out, but with the right sequence of characters you can set it to have additional PHP code run. That would look something like

“; phpinfo(); “

In 2.0.7 the code has been changed to:

43
44
// Google Analytics tracking script
add_action('wpmm_head', array($this, 'google_analytics_code'));

Which calls the new function google_analytics_code():

691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
public function google_analytics_code() {
	// check if module is activated and code exists
	if (
			empty($this->plugin_settings['modules']['ga_status']) ||
			$this->plugin_settings['modules']['ga_status'] != 1 ||
			empty($this->plugin_settings['modules']['ga_code'])
	) {
		return false;
	}
 
	// sanitize code
	$ga_code = wpmm_sanitize_ga_code($this->plugin_settings['modules']['ga_code']);
	if (empty($ga_code)) {
		return false;
	}
 
	// show google analytics javascript snippet
	include_once(WPMM_VIEWS_PATH . 'google-analytics.php');
}

The user input is again run through the function wpmm_sanitize_ga_code(), preventing the possibility that PHP code is included.

Proof of Concept

While logged in as an administrator, on the plugin’s setting page enabled the maintenace mode and set the “Tracking code” setting (on the Modules page) to “”; phpinfo(); “”. When not logged in the frontend of the website will now display the PHP info displayed by phpinfo();

11 Jul

Protecting You Against Wordfence’s Bad Practices: Missing Authorization Vulnerability in WP Maintenance Mode

Wordfence is putting WordPress website at risk by disclosing vulnerabilities in plugins with critical details needed to double check their work missing, in what appears to be an attempt to profit off of these vulnerabilities. We are releasing those details so that others can review the vulnerabilities to try to limit the damage Wordfence’s practice could cause.

Wordfence describes the missing authorization vulnerability in WP Maintenance Mode version 2.0.6 as “This vulnerability allows an attacker with a subscriber level account to modify plugin settings.”.

Like the information disclosure vulnerability that Wordfence mentions in the same post, what we found is that it didn’t actually exist in version 2.0.6. It had existed as of version 2.0.3 and had been fixed in 2.0.4. Strangely both of them had been fixed before Wordfence claims to have even contacted the developer about the vulnerabilities.

In version 2.0.3 the function reset_settings() was accessible through AJAX (in the file /includes/classes/wp-maintenance-mode-admin.php):

add_action('wp_ajax_wpmm_reset_settings', array($this, 'reset_settings'));

That would make it accessible to anyone logged in to WordPress, so a check needs to be done to insure an intended user is doing that, but no check was done:

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
public function reset_settings() {
	if (empty($_REQUEST['tab'])) {
		return false;
	}
	$tab = $_REQUEST['tab'];
 
	if (empty($this->plugin_default_settings[$tab])) {
		return false;
	}
 
	// OPTIONS UPDATE
	$this->plugin_settings[$tab] = $this>plugin_default_settings[$tab];
	update_option('wpmm_settings', $this->plugin_settings);
 
	wp_send_json(array('success' => 1));
}

In version 2.0.4 the function first checks to make sure the user trying to access the function can manage_options, which is a capability only Administrators normally have:

177
178
179
180
181
182
public function reset_settings() {
	try {
		// check capabilities
		if (!current_user_can('manage_options')) {
			throw new Exception(__('You do not have access to this resource.', $this->plugin_slug));
		}

It is worth noting that Wordfence excluded the important detail that the user can only reset the settings to their default values and can not otherwise change them.

Proof of Concept

The following proof of concept will reset the plugin’s settings, when logged in to WordPress.

Make sure to replace “[path to WordPress]” with the location of WordPress.

http://[path to WordPress]/wp-admin/admin-ajax.php?action=wpmm_reset_settings
11 Jul

Protecting You Against Wordfence’s Bad Practices: Information Disclosure Vulnerability in WP Maintenance Mode

Wordfence is putting WordPress website at risk by disclosing vulnerabilities in plugins with critical details needed to double check their work missing, in what appears to be an attempt to profit off of these vulnerabilities. We are releasing those details so that others can review the vulnerabilities to try to limit the damage Wordfence’s practice could cause.

Wordfence describes the information disclosure vulnerability in WP Maintenance Mode version 2.0.6 as “allows a remote attacker to download the list of subscribers from WP Maintenance Mode who have asked to be notified when a site returns to full functionality. To exploit this vulnerability, an attacker simply needs to have a registered account on the victim site with no special permissions.”.

First off, most WordPress website don’t allow the public to create accounts, so saying the attacker “simply needs to have a registered account on the victim site” seems to be misleading at best.

When we went to look for the vulnerability we had a pretty good idea of where it should be, as with this type of issue usually there is a function accessible through AJAX that does not contain the proper checks to make only those intended to access it can. In version 2.0.6 of the plugin, in the file /includes/classes/wp-maintenance-mode-admin.php, we found what seemed to be the relevant function being registered for AJAX access:

41
add_action('wp_ajax_wpmm_subscribers_export', array($this, 'subscribers_export'));

But the connected function was already checking to make sure that lower level users could not access the function by exiting if the user cannot manage_options, which only Administrator level users normally have access to:

117
118
119
120
121
122
123
124
125
126
public function subscribers_export() {
	global $wpdb;
 
	try {
		// check capabilities
		if (!current_user_can('manage_options')) {
			throw new Exception(__('You do not have access to this resource.', $this->plugin_slug));
		}
 
		// get subscribers and export

At that point we went looking for something else that could do this particular export and came up empty. Looking at the changes made between 2.0.6 and 2.0.7 didn’t show any changes that would match this vulnerability. We then went back and found the code that checks the user’s capability was added in version 2.0.4. You can see that in version 2.0.3, the function’s code starts without any check first:

104
105
106
107
public function subscribers_export() {
	global $wpdb;
 
	$results = $wpdb->get_results("SELECT email, insert_date FROM {$wpdb->prefix}wpmm_subscribers ORDER BY id_subscriber DESC", ARRAY_A);

Not only did Wordfence claim the vulnerability was in version 2.0.6, which it wasn’t, but they claimed they notified the developer of the vulnerabilities in the week of June 26 – July 2. Version 2.0.4 was released on June 16, so something is not right here.

Proof of Concept

The following proof of concept will download the subscriber list, when logged in to WordPress.

Make sure to replace “[path to WordPress]” with the location of WordPress.

http://[path to WordPress]/wp-admin/admin-ajax.php?action=wpmm_subscribers_export