Tag: is_tag


WordPress Plugin Security Alert

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 plugin in question is Salon Booking System (link to the WordPress repository)

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:

Note: The “Create a new user” tickbox has not been ticked.

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:

Any identifying marks have been redacted/pixellated to preserve anonymity.

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:

Note: Booking 11330 was also created by the same administrator user, but for another customer.

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:

In this case, full administrator access. If another role had been used to create the booking, that role’s privileges would be available instead.

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.php takes 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)

UPDATE – 28 December 2019: A post on the WordPress Support Forum for this plugin suggests that this patch was not rolled into the Premium version of the plugin.

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:

  1. 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.
  2. 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)

Security isn’t a dirty word, Blackadder

I’m often surprised at just how some websites treat their users when it comes to security.

As any decent website developer knows, one of the basic tenets of application security is that you should never store a user’s password in an unencrypted format – and you shouldn’t really be storing a password in an encrypted format, either. The correct way to deal with storing a password is to use a password hashing algorithm (note: “password hashing”, not just “hashing”)

I’m not going to pretend that I’m perfect at this. It took me an embarrassingly long time to stop using MD5 for hashing passwords in my code (although, in my defence, it was at least salted and not just a straight hash) but I caught up with the zeitgeist and all’s well again. Of course, it seems these days I do most of my work with existing frameworks and applications, so don’t really have to worry too much about that kind of thing any more.

The fact that there are so many frameworks and other tools out there to help devs with this kind of thing just makes it all the more upsetting when I see someone doing it so drastically wrong. Someone like whoever it was that developed KidsPass.co.uk

For those unfamiliar, Kids Pass is a website aimed at parents that offers many discounts and other offers on things to do with their little’uns. It’s not a website aimed at children, which is just as well because with password security as poor as theirs that would be absolutely terrifying.

What’s wrong with it?

The only thing right with it is that the website uses HTTPS, although even that isn’t as good as it could be (it uses an obsolete key exchange, in this case RSA)

Let’s start with the sign-up process:

Nothing too horrific there. You’ll note that it doesn’t ask me for a password, and also that it asks for credit card details. The latter there isn’t that unusual, but with how they deal with password security the thought of them having my card details fills me with the sort of dread that I feel if I ever find myself having to watch The Only Way Is Essex.

When you successfully pay for your membership, Kids Pass send an activation link that you use to complete your registration. More on this below.

Should you wish to continue, it is at this point that you choose your password. Which they then email to you, unencrypted, in plain text.

Still, that doesn’t necessarily mean that they store the password insecurely, right? Could just be that they send it to you after you submit it (which is still horrible from a security standpoint) and then hash it before saving it to their database. Fortunately, there’s an easy way to check.

Ah, the old “forgotten your password” trick

If you forget your password, a decent website will give you a link to click on where you enter your email address or username, and it will send you a “token” to that email address that can be used precisely once to change your password to something else. A truly great website will make the use of that token time-limited as well, usually 24 hours.

What Kids Pass do is this:

(note: I have changed my password for the purposes of this image, I’m not that silly)

Yep, they send you your password – as a reminder – completely naked and unencrypted. Sending passwords like this is bad enough, as SMTP (the method used to send email) is completely and utterly insecure, and could quite easily be pulled out of the ether by an attacker.

The real danger though, is that they are clearly storing those passwords either in plain text form, or in a way that they can be decrypted. The latter is better than the former, but not by much because it wouldn’t take a skilled hacker with access to their server long to figure out how the decryption process works, and when that happens they may as well not be encrypted.

I’ve sent emails and tweets to Kids Pass about this before (back in December), and have had no reply whatsoever.

Let’s go back to that activation process

When I first started writing this post, the goal was to talk about the lax password security – but in the process of writing it and performing the various actions contained within, I discovered something far more serious. Remember the activation link that they sent me earlier on? Well, it contains a unique identifier that ties the activation link to your registration, and the activation page looks like the below:

Note: I’ve removed my activation code, changed its format and redacted any personal details from the screenshot above.

The problem with this is that the activation code is entirely predictable – it’s not a 32 character token like many websites use, and all you need to do to access other people’s personal data is change the code in the URL that the link takes you to. It would be horrifically easy for someone to abuse this and harvest personal details – names, email addresses and postcodes.

This is unacceptable – not only is it a serious violation of the basic principles of information security and the Data Protection Act, but it’s inexcusable for a developer in this day and age to have not thought this through to that degree.

It should not be trivially easy to get access to other people’s personal information.

Oh, and about those credit card details

Because they collect credit card details directly on-page through their site, unfortunately there’s no way for me to identify who they use to process card payments. The server does do some sort of validation on the card details, as it spits out an error if I use an obviously fake card number, or a test number (4242 4242 4242 4242 for example)

The submitted number will clearly end up on their server somewhere though, be it in memory or in a logfile somewhere. Hopefully they don’t store the card details on the server for manual processing later on, or indeed for any reason whatsoever – because if they do, I shudder to think about how they’re storing them based on the revelations above!

They’re not the only ones

I wish they were, but of course they’re not. It’s frighteningly common – just take a look at Plain Text Offenders.

Sort it out, Kids Pass – before you have a serious security breach.