11 Jul 2016

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();

Leave a Reply

Your email address will not be published.