Race Conditions
Many applications in the wild utilize restricted features. Examples of these features include invite codes limited to X amount of friends, a single coupon code, or discounts that are only applicable once per account. What do you think would happen if we were able to bypass these restrictions? What if a benign business feature was capable of being your next critical finding?
Well, this article hopes to shed more light on what we know as "Race Condition Vulnerabilities." Before we dive into the topic, let's quickly educate ourselves on some basic terms to aid our understanding of this vulnerability.
A Brief Overview
Let’s imagine that a browser application is developed and installed on your computer. Your computer will then assign internal memory to run this application, which is referred to as a “Process.” The process will now be understood by your computer as the execution of this particular browser program.
The browser has various features such as, taking input from the keyboard, making internet searches, and showing you output. All of these are individually called “Threads.” A thread is simply a unit of execution inside a process. It is a subset of processes where it shares the same memory. If the threads are executing simultaneously, it is called “Multi-Threading.” So based on our example, our browser running simple features would require several threads, which our computer will process simultaneously. So, something as-simple-as opening up a browser to start your work day includes multithreading.
"Multi-processing" is a technique where several processes execute concurrently using multiple processors. The benefit of multiprocessing is increasing the system's speed and the allocation of tasks between processors. Think of it as splitting up the work.
A "Scheduler" helps the system to arrange multiple threads in some order to execute them.
This is only a brief explanation, but it brings you up to speed on the mechanics of how this vulnerability works.
When do Race Conditions Happen?
In a computer system, the scheduler is in charge of which threads to process in order. However, if any of the threads were executed out of order - then it happens, a race condition is created. This could cause our application to lose track of one of the threads. To aid in visualization, we can provide this example.
Assume you have developed a wallet app, and in this scenario our customer is withdrawing two different times.
Thread A | Thread B | Wallet Balance |
---|---|---|
Access the Application | ||
Login to Application | ||
Read Available balance | 100 | |
Withdraw Amount 10 Euros | 100 | |
Current Balance | 90 | |
Read Available Balance | 90 | |
Withdraw Amount 20 Euros | 90 | |
Current Balance | 70 |
After two transactions the balance of our wallet should be 70 euros. There doesn't seem to be any problems.
However, let's use this same example, but assume the app experienced a race condition.
Thread A | Thread B | Wallet Balance |
---|---|---|
Access the Application | ||
Login to Application | ||
Read Available balance | 100 | |
Withdraw Amount 10 Euros | 100 | |
Current Balance | 90 | |
Read Available Balance | 90 | |
Withdraw Amount 20 Euros | 90 | |
Current Balance | 90 |
After two transactions, the value is 90 euros in the wallet because the two different threads (in bold) are processed concurrently. The end result being a miscalculation in total funds.
Understanding Race Condition Vulnerabilities
If an attacker used our hypothetical race condition above to perform malicious operations and help bypass secured mechanisms, it then becomes "Race Condition Vulnerability." This vulnerability commonly occurs when threads use the same shared memory to update the values of variables.
Additionally, race conditions require sending many requests in a short amount of time. The rush of requests is essential in triggering the race condition to happen. The confusion is what causes the threads to become derailed.
This type of attack can be accomplished with tools like "Intruder" (or the Turbo Intruder plugin) in Burp Suite, that allow a pentester to run this particular vulnerability.
The most likely targets for this attack being:
- Applications related to money transfers (adding money or subtracting money)
- Incrementing and decrementing areas primarily concerned with credit scores, coupon codes, discounts, and followers in social media apps
- Logins using OTP or any codes to enter into the application
So, what are some real world examples of this happening?
Case Study 1 (Microsoft Rate-limiting)
In general, Microsoft has implemented rate-limiting for the forget password page to avoid brute force attacks. This aids in the prevention of an account takeover by limiting attempts. However, a researcher recently shared a theoretical demo illustrating how it is possible to achieve account takeover, regardless.
Initially, researcher Laxman Muthiyah found that out of 1000 requests, only 122 are processed by the server. The remaining are shown as an error because of rate-limiting mechanisms. If someone can send all the combinations of 6 and 7 digit security codes (nearly 11 million) concurrently, an account takeover is possible. This is no easy feat, considering it requires a lot of computational power. This issue was patched in November 2020 by Microsoft.
In summary, the rate-limiting feature implemented on the forget password page was bypassed using a race condition.
Case Study 2 (Starbucks Free Coffee)
Who doesn't love coffee? Well, Egor Homakov found a race condition vulnerability on the gift card page of Starbucks, allowing him to generate unlimited amounts of credit for free.
The vulnerability exists by transferring the amount from gift card wallet 1 to gift card wallet 2. A race condition is executed on this feature by sending multiple requests in a small time frame. This allowed the researcher to get more credit in wallet two than he previously had on wallet 1.
Egor disclosed this to their security team back in 2015.
Recommendation
The processing of multiple requests simultaneously and shared Database Management Systems (DBMS) are prime culprits for race condition occurances.
So, what's the fix?
We need to implement a pause and execute method. In other words, implementing resource locks for the shared memory and creating a condition that requires each request be processed a single time. The remaining requests will be in a paused state, as if they were in a queue.