Race conditions are vulnerabilities that many security researchers first encounter in training labs. They are simple to understand, yet they still appear in real-world applications. Even though the technique is straightforward, fixing it can be challenging for developers because it requires proper handling of concurrent requests.

In this writeup, I'll share how I discovered and exploited a race condition in a user management feature. we'll refer to the affected website as target.com.

The Discovery

While exploring the features of target.com, I noticed that a standard account was allowed to add only one additional user. To add more users, the platform required upgrading to a business subscription. This made me curious about how this restriction was enforced.

Using Burp Suite, I intercepted the request responsible for adding a new user. The request had the following structure:

GET /<username>/!users/<user_id>/?sidn=<session_token>&<cache_buster>= HTTP/2
Host: target.com

Sending this request once worked as expected, and a single user was added. Any further attempts were rejected because the account had reached its limit. Everything seemed to function correctly at first glance.

A Familiar Idea

This scenario reminded me of a PortSwigger Web Security Academy lab that demonstrates how sending multiple requests at the same time can bypass server-side checks. The idea is simple: if the server checks the user limit before updating the database, several requests sent simultaneously might all pass this check.

Although the technique is simple, mitigating race conditions is not easy. Developers need to ensure that critical operations are handled atomically, which often requires database locking or other synchronization mechanisms.

Turning the Idea into Practice

I decided to test whether this technique would work on target.com.

First, I intercepted the "add user" request and sent it to Burp Suite's Repeater. Then, I duplicated the request around 20 times and grouped them together. Using Burp's Single Packet Attack, I sent all the requests at the same time.

The first attempt did not produce any unexpected results — only one user was added. However, knowing that race conditions depend heavily on timing, I tried again. On the second attempt, around four users were successfully added. In another attempt, only two users were created.

This inconsistent behavior confirmed the presence of a race condition. Not all requests succeeded because the server processed some of them after the user limit had already been updated. This is a typical characteristic of race condition vulnerabilities: the outcome varies depending on timing and server conditions.

Why It Works

The vulnerability exists because the server likely performs the following steps:

  1. Check if the number of users is below the allowed limit.
  2. If the condition is true, proceed to create the new user.
  3. Update the user count.

When multiple requests arrive at nearly the same time, several of them may pass the initial check before the database is updated. As a result, more users are created than intended, even though not all requests succeed.

References