Featured image of post Change Other People's Avatars on a Retail Website

Change Other People's Avatars on a Retail Website

Why you should ensure your endpoints' authorization and authentication have been thoroughly tested.

Introduction

This particular online retail store has an avatar feature for user accounts. As in, your account has a little picture (an avatar) that is associated with your account. One of the places this little picture is used is when you write a testimonial or product review; it is placed next to your name.

An example of a product review with an avatar. Choo choo.

Anyway, one can change their own avatar by going to their “personal settings” page. You’ll find that there are two options relating to your avatar there: you can upload a custom avatar, or reset it back to the default.

The options available to set or reset your avatar.

A Primer on User IDs

Each account on this website has a unique positive integer known as a user ID. You can find your own number by going to your account dashboard and looking at your account number. Your user ID is the number part after the letters.

In this case, the user ID is 1973851.

You can also find other people’s user IDs scattered about the DOM. For example, inspecting the button to report a review will reveal the user ID of the reviewer (pictured).

The 'data-uid' attribute contains the user ID of the reviewer.

There are many other ways of finding out the ID of a user; this is just one of them. Armed with someone else’s user ID you can now perform some actions on their behalf.

Resetting Any Avatar

Let’s assume there is another account on this website that has an avatar (I imagine there are lots of these, in fact, the majority of the accounts on the site are likely not yours). Let’s also assume that you know the user ID of that account.

All you need to do now to reset someone else’s avatar is to make an HTTP POST request to /code/ajax_avatar_reset_pdo.php with the body uID=123456 where 123456 is the user ID of that other user. You should receive back a JSON success message. This is the same request that is made when you reset your own ID except that you are providing someone else’s ID instead.

This is probably less than ideal. Imagine if you took great care in selecting an avatar and then one day you find that your picture had been reset to the default avatar. That would suck!

Setting Any Avatar

The second issue is that you can essentially do the same thing but instead of resetting an avatar, you can instead set it to anything you want.

All you need to do here is to send a POST request to /code/ajax_avatar_upload_pdo.php with the following body (and a multipart/form-data content-type header with the appropriate boundary) and you’ll be able to change the avatar of any user on the site. This endpoint returns a success message as well as the filename of the image that can be used to build the URL for it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-----------------------------263549667038225259264045948163
Content-Disposition: form-data; name="uID"

INSERT TARGET USER ID HERE
-----------------------------263549667038225259264045948163
Content-Disposition: form-data; name="avatarImage"; filename="meme.jpg"
Content-Type: image/jpeg

INSERT RAW IMAGE DATA HERE
-----------------------------263549667038225259264045948163--

As you can imagine, this issue is much more problematic than the previous one.

An aside about Firefox Devtools

For some reason, when I was using Firefox’s devtools to replay the request, the image gets partially garbled. I cannot figure out why this happens but I suspect it is a bug to do with the binary encoding/decoding in the replay panel.

My avatar (left) compared with the avatar I uploaded for my other account (right).

However, I can confirm that this is just a quirk of Firefox.

No Auth Here

Reading this post you might be thinking “okay, well this is obviously a case of the backend not checking the ID of the signed in user”, and you’d be right… kinda. It’s a bit more complicated than that, because you don’t actually need to be signed in at all! It turns out these endpoints are completely unauthenticated. Yeah, you can just fire off an anonymous request to perform these actions 😬.

A demonstration of the avatar upload.

Change an Avatar with cURL

Assuming that you have an image named “meme.jpg” in your current directory, and your target user’s ID was “???”, then the following will allow you to change their avatar. This is how I was able to confirm that the image corruption issue earlier was related to Firefox’s devtools.

1
curl -X POST https://www.website-name.co.nz/code/ajax_avatar_upload_pdo.php -F '[email protected]' -F 'uID=???'

Reset an Avatar with cURL

Similarly, an avatar can be reset:

1
curl -X POST https://www.website-name.co.nz/code/ajax_avatar_reset_pdo.php -F 'uID=???'

Look ma; no creds!

Potential Impact

Because of the following factors, this issue could be pretty serious:

  1. There is no authorization layer on these endpoints.
  2. There is no authentication layer on these endpoints.
  3. All user IDs are just postive integers.

It would be trivial for one to write a script to iterate over all the users on the site, changing or resetting them as they wish. I imagine this would cause more than a few headaches for this website’s support team. Furthermore, it does not appear that old avatars image files are actually deleted from this website’s servers (free cloud storage, anyone?).

Disclosure

At this point in time I already had one support email in flight requesting an audience with this website’s support team, so I figured I’d try and get their attention with a different tactic. This website has a sister company that offers “Penetration testing and Vulnerability assessments”; at the bottom of that page was a contact form that I used to notify them of this issue.

Perhaps they need to hire themselves, eh?

After reaching out via the form on the website on the 24th of March 2024 I received a response on the 27th of March. These issues were fixed within a few days of being reported. Their solution was to remove the uID request parameter and instead just make the endpoints operate on the signed-in user. This is a good solution to the problem; in the general case it doesn’t make sense to pass in a user ID when making these kinds of changes.

Cover photo by Ben Sweet on Unsplash