Featured image of post Helping Oneself to the PII of 800,000 Users and More

Helping Oneself to the PII of 800,000 Users and More

Why the server should never trust any data supplied by the client.

Introduction

Today’s post is about a website that hosts shopping catalogues for many brick-and-mortar retail stores. You’ve probably heard or purchased from some of these stores before: Bunnings, Woolworths, Briscoes, etc. This website also has a very “late 00s” vibe, both from the frontend UI and the behaviour of the backend.

The frontpage of the website.

This website also has an Australian site that fills the same niche for the Australian market. I’ve found quite a few issues with these websites and so I’ve decided to present them all in the one post.

Information Disclosure

Due to how the server is configured we can extract quite a bit of information about the software running on the backend.

In a Stacktrace

Catalogues are embedded into other websites by a service running on “embed.website-name.co.nz”. A website such as Smith’s City will make a request to https://embed.website-name.co.nz/catalogues/view/56/ which returns some JSON with information about the catalogue in it. If we manipulate the URL to induce an error, for example by removing the last segment of the URL we get this error page:

The error page returned by https://embed.website-name.co.nz/catalogues/view/

This gives us quite a bit of information about what the system is running:

  • The programming language used: PHP.
  • The operating system that the server is running on: some kind of *nix.
  • The location of the static content: /var/www/vhosts/embed.website-name.co.nz/public_html/
  • The location of the PHP app: /var/www/vhosts/embed.website-name.co.nz/application/
  • The web framework in use: CodeIgniter.

This is far more information than one would want to give away about the system since it could be used by an attacker in conjunction with some other kind of attack. Ideally, this information would never make its way to the end user; it should be logged instead. This is most likely a configuration issue that can easily be fixed.

In the Headers

The above is useful information, to be sure. However, we also get quite a bit of information from the response headers of a normal HTTP request:

These headers are returned with every HTTP response.

From here we can ascertain:

  • The web server and its version: Apache v2.4.58
  • The PHP version: 7.4.33
  • The OpenSSL version: 1.0.2k-fips

This information could be used to look for vulnerabilities that can be used on this website. At the time of writing, this version of Apache is the latest stable version and is about 6 months old. This version of PHP is the latest version available for the 7.4 series and was released in November 2022 (about 16 months ago).

However, this version of OpenSSL is very very dated. It was released in January 2017; more than 7 years ago! It’s also not the latest release in the 1.0.2 series and so should at least be patched to the latest version. We can also guess that the operating system could be Red Hat Enterprise Linux (RHEL) since this seems to be the only operating system currently using this specific version of OpenSSL as its latest.

This information should absolutely be stripped out of the headers before being returned to the user. As mentioned in the previous section, by knowing this information an attacker has a much easier time finding vulnerabilities for your software. Most web servers will allow you to strip these headers out before they get sent down to the client.

Cross-Site Scripting

Just like all these websites, this website also has a cross-site scripting vulnerability. It seems like websites just can’t get enough of allowing the user to customise their user experience; that’s pretty generous.

Via the User’s Name

Firstly, we can change our first name to include arbitrary HTML tags… including scripts. This isn’t particularly useful for an attacker since the users are isolated from each other.

Cross-site scripting allows arbitrary HTML (including JavaScript) to be rendered on each page load.

This happens because the user’s name in the top-right hand corner of the page isn’t HTML escaped when it’s rendered by the backend.

Via Email

This website allows its users to add products from multiple catalogues to a “shortlist” that can then be shared with others via email or a printout. However, this site actually goes one step further here and will facilitate the sending of the email for you. All you have to do is provide the details of the user you wish to send it to and the email address you want them to reply to and you’re away laughing.

The shortlist form that you can fill out.

And when you fill it out properly you get an email that looks like this:

A perfectly normal email.

Unfortunately, this email sending system is also vulnerable to XSS! For example, we can use some carefully crafted values in the form to strip out much of the email content that is added by the backend. We could use this to send a malicious email to a user of the site:

Adding malicious values to the form.

Which would look like this when sent to the victim’s inbox:

I think I'd even fall for something like this!

Also, the fact that you can just send an email from this system without any CAPTCHA or other bot protection is a bit concerning. This could be abused by spammers to send floods of mail to unsuspecting netizens. In general, don’t offer to send emails on behalf of the user; this feature could also be implemented by opening the user’s email client. However, if the web server must send email on the behalf of a user, then the user’s inputs should be sanitised and some spam protection should be employed.

The Excessively Helpful Sign In Form

Like many websites, this website has an administration panel that allows the owners to perform site maintenance. However, unlike most websites, the sign in form actively ushers the user into putting the correct details in:

I, for one, love the user-friendly sign in panel.

After a bit of playing around we have discovered that the following rules exist:

  • “All form fields are required”
  • “Username may consist of a-z, 0-9, underscores, begin with a letter.”
  • “Length of username must be between 3 and 24.”
  • “Length of password must be between 5 and 24.”
  • “Password field only allow : a-z 0-9”

This is bad because we have given an attacker all the information they need to conduct a dictionary attack or brute-force attack on the site. A website should return as little information as possible about the username and password requirements.

Leaking the Personal Information of All Users

This is the most serious issue that I managed to find. As long as you are signed in, you have the ability to view the personal details of any other user of the site.

When you sign in to the site, you’re issued several cookies by the server. One of these cookies is called userId and it contains the ID of the signed in user.

A view of all the cookies on the website.

Unfortunately, this userId cookie is what the server uses as the source of truth for its authorization. So if I’m signed in as “User 1” with the ID 670986 I can change that cookie to the ID of “User 2” which is 670987. When I reload the page, the header still shows up as myself being “User 1”. But if I navigate to the user details page, I am shown the details from “User 2”.

As you can see in the header and form, the website thinks that I'm both 'User 1' and 'User 2'.

You may notice that I can view the following details about the other user:

  • Name
  • Email
  • Age
  • Gender
  • Location
  • Phone Number

From a privacy standpoint, this is pretty bad. It’s made worse because all the user IDs are positive integers, which means that in theory, anyone can scrape all the personal details of the ~670,000 user accounts on the New Zealand site and ~150,000 accounts on the Australian site. That’s about 800,000 user accounts that have been sitting exposed!

From an investigation standpoint, this presents some tricky issues for the site administrators since the changing of the cookie likely won’t show up in any access logs. They may not be able to tell whether or not this has vulnerability has been exploited 🫤.

The takeaway here is that the user ID cookie should be verified in some way. A modern approach would be to implement JSON Web Tokens (JWTs), although I admit that this approach is likely overkill. In reality, the cookie may not even need to exist since the backend should be aware of the user’s ID via the server session.

Disclosure

I reported these issues to the email address on the website on the 1st of April 2024; probably not the April Fool they were expecting. After getting no response, I reached out to the owner/developer of the site on the 9th of April, this time via a LinkedIn private message. I got a response very quickly from the owner and sent them the details of all the issues I had found.

The issue with the user accounts was fixed within the week. Now if you attempt to change the userId cookie to something else then your session will be terminated and you’ll have to sign in again. Some of the other issues were not fixed. My attempts to discuss this with the owner were ignored.

Cover photo by Jason Dent on Unsplash