Gareth Griffiths; web developer, petrolhead, cat owner and all-round good egg.
This is my poky little corner of the Internet. Originally started in 1999 (my website that is, not the Internet) it took on many forms – until 2005, when I lost enthusiasm for my own Internet presence and simply stopped updating it.
This is my (not quite as successful as I’d hoped) attempt at getting some of that enthusiasm back. Enjoy!
Earlier this week, a friend (who, for the purposes of this post is named Stephen) relayed a story to me of an email that one of his friends had received recently.
This friend – let’s call him John – runs a local business that requires advance bookings from customers. He runs his entire booking calendar through his website, and as well as customers being able to register an account and book through the website, he can also create bookings on behalf of customers from the administration panel.
It’s this latter scenario that gave rise to the situation I’m about to delve into. John had booked this customer in to his calendar through the admin panel of his WordPress-powered website, and the customer received a confirmation email containing a “Manage Account” link.
The customer clicked this link to view his booking details, and was surprised to find that he could see a lot more bookings than just his own! He, very responsibly, emailed John to let him know, who then in turn got in touch with Stephen (who had built the website) and Stephen then relayed this whole story to me.
Immediately my ears pricked up – that’s not a great situation for a customer to find, as it suggests deep-seated problems with the website’s security, so I volunteered to take a look. The following is what I found…
Is this a WordPress problem?
Firstly, let’s get this out of the way. This is a security issue with a specific WordPress plugin – NOT WordPress itself.
What is the plugin affected by this security alert?
The original report from Stephen and John related to version 3.30.7 of the Premium version of this plugin, but I have confirmed that the problem exists in version 3.32.2 of the free plugin found on the WordPress repository.
What is the problem?
In short, in situations where the administrator of a website is creating a booking for a customer in the back-end of WordPress – if the admin chooses NOT to create an account for the customer, the confirmation emails (and any subsequent emails relating to that booking) that are sent to the customer’s email address include a link that, when followed, logs them into the WordPress user account that created the booking.
When the link is clicked, the customer is taken to a page that lists all bookings created by the user that created the booking – and, if the customer then goes to the /wp-admin URL (or uses the Dashboard link in the admin bar if it is present at the top) they have access to whatever the original creator of that booking does.
Essentially, this is a privilege escalation vulnerability that may result in personal data being leaked or unauthorised actions being carried out in the WordPress dashboard.
So, how is this happening?
With permission from John, I’ve collated some screenshots of the process – with certain details redacted to protect the anonymity of him, his website and any customers affected.
First, the booking needs to be created by an administrator user:
This creates the booking (which is a custom post type) and sets the post_author ID to the currently logged in user, as is pretty much standard with WordPress.
An email confirmation is then sent to the email address specified, in this case my Gmail account. This email confirmation looks like this:
The Manage Account button is what we’re interested in here. That button links through to this URL: https://[redacted]/?sln_customer_login=b2a96dc6
This is what we see at that URL:
Luckily, on this website at least, there doesn’t appear to be any way of seeing any other customers’ details (although the Update Your Profile link visible in that screenshot does allow the customer to see the administrator’s full name, email address, mobile phone (if present) and postal address (again, if present)
More problematic however, is that if the customer then goes to /wp-admin then they have access to the WordPress dashboard as the user that originally created the booking:
Not good at all – in this case, this would allow the personal details of any registered user or any previously booked customer to be viewed. It also obviously allows the user to edit whatever they want to on the site, if the original user has administrator privileges.
The problem code
This issue stems from the “one click login” facility that is present on the “Manage Account” button. Let’s delve into the code! I’m using version 3.32.2 of the free plugin for this analysis.
The Manage Account button can be found in views/mail/_customer_manage_buttons.php:
Line 37 appends the offending sln_customer_login parameter to the button link, with a value taken from $customer->getHash() – this can be found in src/SLN/Wrapper/Customer.php:
This code attempts to retrieve a meta field from the customer’s user, and if it is not found, generates a new one (using the generateHash() method underneath)
The hash is the first 8 characters of the MD5 hash of the concatenated string of the user’s ID, a colon (:) and the current time as a Unix timestamp (i.e. the number of seconds since Jan 1, 1970) – importantly, this hash does not change unless the existing hash is removed at any point.
When the button is clicked, some code in src/SLN/Action/Init.phptakes over if the sln_customer_login parameter is detected:
As can be seen on line 206, the code fetches the Customer record by the hash described above, and if it finds a matching user ID it fetches the WP_User object, sets the authorisation cookie (using wp_set_auth_cookie) for that user and then runs any actions hooked into wp_login. It then redirects the user to the My Account page.
At this point, it’s essentially game over as the user is now logged in as the user that created the booking.
Critically, because the hash for the administrator users are constant once generated (unless they are removed through some other means) every email sent that relates to a booking created by that administrator from that point on will contain the vulnerable URL.
What can I do to prevent my site being exploited for now?
The plugin author has already published a patch to the WordPress plugin repository (v3.32.4) which fixes one element of the problem – namely the post_author being set to the administrator user in the scenario described. The fix sets the post_author to zero, meaning the Manage Account button is not added to the outgoing emails.
So, to begin with, we recommend updating your plugin to the latest version – I assume that a similar patch has been released for the Premium version of the plugin (Salon Booking System Pro)
However, this fix does not yet prevent emails that have already been sent from potentially being exploited.
A number of possible courses of action exist:
The easiest solution is most likely to create a new user in the WordPress dashboard that effectively replaces any accounts previously used to create bookings, and to then change the Role of the old accounts to Subscriber or another role that has no admin privileges.
The best course of action, if you are familiar with database queries, would be to remove the generated hashes of any non-customer users.
Either way, until the vendor fixes this issue in a satisfactory manner, I would strongly advise against creating new customer bookings without ticking the “Create a new user” box – i.e. make sure that you create a new user for every customer!
Disclosure Timeline (all times are GMT+1)
3rd October 2019
10:59 – contacted the plugin vendor asking for a contact to send further details to
11:12 – vendor responded asking for further details
11:23 – sent detailed description of issue to vendor
11:40 – vendor confirmed receipt, and advised that they would investigate
12:49 – as per responsible disclosure guidelines, advised vendor that I would be preparing a disclosure report that would be made public after 60 days, or when a fix is published
13:17 – vendor advised that they are already working on a fix
4th October 2019
12:23 – vendor advised that they have released a fix on the WordPress plugin repository (v3.32.4) that does not display the button on the emails – the fix sets the post_author to zero on bookings created without an attached user
13:03 – suggested to vendor that code should be added to invalidate existing hashes to prevent emails that had already been sent from being exploited.
13:51 – vendor replied to state that they would attempt to find a solution for the existing hashes.
2nd December 2019
60 day window expires, disclosure report is made public (err… except, I forgot to unprotect the article, so this didn’t happen until the 28th December)
Update: Version 1.5 released on 14th August 2020, on the plugin repository. See changelog on the plugin repository for more information.
This, my third WordPress plugin (and also my third WooCommerce plugin, apparently I really like doing stuff with WooCommerce!) adds a postcode lookup tool to the checkout in WooCommerce 3.x that lets your UK-based customers quickly fill in their billing/shipping addresses based on their postcode.
It utilises the great getAddress.io API for the postcode lookup, and as such requires an API key for their service. I’d always recommend the £10 per month pricing plan, simply because it offers the best value, but they have a number of others available.
Adds postcode lookup for both billing and shipping addresses
Administrators can turn off the lookup for either address, if needed
(v1.1) Uses an internal cache for each customer to prevent repeated calls for the same postcode eating up API usage limits
Correctly uses the WooCommerce “locale” system to show and hide the lookup button when appropriate
Has full internationalisation (i18n) and localisation (l10n) support – so if you want to translate the plugin into your own language, please get in touch!
The plugin adds a Find Address button next to the postcode field, and moves the Postcode field so that it is the first field filled in after selecting United Kingdom as the country.
When the Find Address button is clicked, the user is presented with a menu of available addresses:
In the WordPress back-end, administrators have the following settings available:
As with all WordPress plugins, you just need to drop the plugin folder into your wp-content/plugins folder and then activate the “GazChap’s WooCommerce getAddress.io Postcode Lookup” plugin.
I’m using WordPress 5.1 and WooCommerce 3.5.5 – please let me know in the comments if it doesn’t work on other versions. I believe it requires at least WooCommerce 3.0.0.