Introduction
Race conditions are one of those vulnerabilities that are often overlooked but can have devastating consequences when exploited correctly. In this TryHackMe room, we explore how attackers can manipulate the timing of operations to gain unauthorized access or perform unintended actions.
This guide walks you through the room step-by-step, explains the underlying concepts, and shares some personal thoughts on why this vulnerability is both fascinating and dangerous.
What is a Race Condition?
A race condition occurs when a system's behavior depends on the sequence or timing of uncontrollable events. In web applications, this often means two or more processes accessing shared resources (like a database or file) simultaneously, leading to unexpected behavior.
Real-World Analogy
Imagine two people trying to withdraw money from the same bank account at the exact same time. If the system doesn't lock the account during the transaction, both might end up withdrawing more than the available balance. That’s a race condition.
🛠️ Room Walkthrough
Multi-Threading Section
Here are the vital concepts that we have to understand:
A program is a set of instructions to achieve a specific task. You need to execute the program to accomplish what you want. Unless you execute it, it won’t do anything and remains a set of static instructions.
A process is a program in execution. In some literature, you might come across the term job. Both terms refer to the same thing, although the term process has superseded the term job. Unlike a program, which is static, a process is a dynamic entity. It holds several key aspects, in particular:
Program: The executable code related to the process
Memory: Temporary data storage
State: A process usually hops between different states. After it is in the New state, i.e., just created, it moves to the Ready state, i.e., ready to run once given CPU time. Once the CPU allocates time for it, it goes to the Running state. Furthermore, it can be in the Waiting state pending I/O or event completion. Once it exits, it moves to the Terminated state.A thread is a lightweight unit of execution. It shares various memory parts and instructions with the process.
In many cases, we need to replicate the same process repeatedly. Think of a web server serving thousands of users the same page (or a personalized page). We can adopt one of two main approaches:
Serial: One process is running; it serves one user after the other sequentially. New users are enqueued.
Parallel: One process is running; it creates a thread to serve every new user. New users are only enqueued after the maximum number of running threads is reached.
Now let's answer the two wrap-up questions of this section:
Race Conditions Section
Generally speaking, a common cause of race conditions lies in shared resources. For example, when multiple threads concurrently access and modify the same shared data. Examples of shared data are a database record and an in-memory data structure. There are many subtle causes, but we will mention three common ones:
Parallel Execution: Web servers may execute multiple requests in parallel to handle concurrent user interactions. If these requests access and modify shared resources or application states without proper synchronization, it can lead to race conditions and unexpected behaviour.
Database Operations: Concurrent database operations, such as read-modify-write sequences, can introduce race conditions. For example, two users attempting to update the same record simultaneously may result in inconsistent data or conflicts. The solution lies in enforcing proper locking mechanisms and transaction isolation.
Third-Party Libraries and Services: Nowadays, web applications often integrate with third-party libraries, APIs, and other services. If these external components are not designed to handle concurrent access properly, race conditions may occur when multiple requests or operations interact with them simultaneously.
So, for the next two questions we need to focus on the first image and the last one. You'll get the two answers without problems.
Web Application Architecture Section
Client-Server Model: The Foundation
Web applications operate on a client-server model:
Client: This is typically your web browser or mobile app. It sends requests to the server—for example, when you click a button or submit a form.
Server: This is where the application lives. It receives the client’s request, processes it, and sends back a response—like an HTML page or a confirmation message.
These interactions happen over a network, and while they may seem instantaneous, there’s always a small delay—this delay is where race conditions can sneak in.
The Three-Tier Architecture
Most modern web apps follow a three-tier architecture:
Presentation Tier: The front-end—HTML, CSS, and JavaScript rendered in your browser.
Application Tier: The back-end logic—written in languages like Node.js, Python, or PHP—that processes requests and applies business rules.
Data Tier: The database—MySQL, PostgreSQL, etc.—that stores and retrieves data.
Each tier plays a role in how data flows and how decisions are made, which is crucial when we talk about timing and state transitions.
Exploiting Race Conditions: Machine 1 - Money Transfers
When you transfer money, the app typically:
Checks if your account has enough funds.
If yes, it proceeds with the transfer.
If not, it shows an error.
This seems like a simple two-state system: Transfer Not Sent → Transfer Sent.
But in reality, there’s a hidden third state: Checking Balance. This is a brief moment when the app is verifying your funds but hasn’t yet committed to the transfer. If an attacker sends multiple requests during this window, they might bypass the balance check and transfer more than allowed.
After starting the Machine and the AttackBox, let's jump into Firefox browser and input the MACHINE_IP:PORT
that is given in the section. Mine is http://10.10.212.218:8080
. We need to open two different window tabs here and log in following this logic: Tab1 -> Account1, Tab2 -> Account2
This is the first line that is shown
This is the second telephone line shown
Now we need to fire up our BurpSuite and let the tool intercept via FoxyProxy the session that we are working with. We need the plugin installed and configured on our browser to achieve this task.
Starting with the first account which is 07799991337, head over the Pay and Recharge button and press it. We will be landed on the section showed in the next image.
From this point on, we need to complete the form with the other line number and input an import like 5$ to transfer. Here's the example before the submission, DON'T hit Transfer! We have to set up our proxy interception first.
Let's go to the plug-in section of our browser and set FoxyProxy from Disable to Burp proxy. Head over Burpsuite and check that the intercept is ON in the proxy section. After all those checks it's time to hit the Transfer button.
It caught the POST request with juicy information. Now we have a clear look on what field is passed and how. Click the right button and then Send to the Repeater. Since we're performing a race condition we have to do a little bit of hacking behind what we've learnt so far.
In the Repeater section with our tab 1 opened, we need to send in parallel 20 of those requests in order to score the flag and the amount of 100 dollars on the second telephone line balance. In order to achieve this hit the + near Tab 1 and select Create Tab Group, input a name as you prefer and add the Tab 1 to the group. Then hit Create.
We've created our tab group, now we to click the Arrow near the Tab Group name "Group 1", so we will check that Tab 1 is inside. Now right click on Tab 1 and Duplicate Tab. Select 20 times and then you'll make sure that our tab group now contains 21 Tabs. Before hit Send button we need to check that we've enabled the parallel sending request, otherwise with other options like single connections, separate connections, etc.. IT WON'T WORK!!
In case i've forgot to mention, arrow down under Send button and you'll see this menu here...
With FoxyProxy still on and the intercept on in Burpsuite we can hit t Send in Parallel. After we receive a response with transaction-successfull
code we can go ahead and take the intercept off in Burpsuite. Now let's go to the freezed browser page and let's refresh it. It will appear something like this unusual error.
This is something we want to achieve in order to score the flag from the other account, now try to log in with 071 phone line and check the balance.
Perfect everything worked fine, THM{PHONE-RACE} is our flag. You can reverse the entire procedure for the balance going from the second to the first phone line but it's not mandatory to complete this task.
WARNING! Don't get frustrated if you failed the first time, this actually had taken me various days in order to get the flag, various restarting of the Machine and AttackBox. Keep on trying.
Detecting and Mitigating Race Conditions
From a business owner's point of view, spotting race conditions can be tricky. For example, if a few users manage to redeem the same gift card more than once, it might go unnoticed unless someone is actively monitoring the logs for unusual activity. Since race conditions can also be used to exploit more subtle system flaws, it's important to involve penetration testers and bug bounty hunters to help uncover and report these issues.
Here are some common ways to prevent race conditions:
Synchronization Mechanisms: Programming languages offer tools like locks to ensure only one thread can access a shared resource at a time.
Atomic Operations: These are operations that run completely without interruption, ensuring that no other thread can interfere while they’re being executed.
Database Transactions: Transactions group multiple database actions into a single unit. Either all actions succeed together, or none do—this helps maintain data consistency and prevents conflicts from simultaneous access.
Now we can look for the last challenge in this room, which is...
Exploiting Race Conditions: Machine 2 - Bank Transfers
The key takeaway is this: race conditions exploit the time gap between state transitions.
If a malicious user can send multiple requests during that tiny window—often just milliseconds—they might trick the system into applying a coupon multiple times or transferring more money than allowed.
Step-by-Step Exploit
Quit the first machine and start this last one. After few minutes will be displayed MACHINE_IP:PORT
, that in my case is 10.10.204.217:5000
. Let's copy and paste this into our Firefox url bar. We will be landing in a log in page. Here we can log in with the first account credentials showed as Rasser Cond.
We noticed that the account balance is $100, our action logic will be transferring funds from Rasser to Zadowni and a second hop from Zadowni to Warunki in order to score more than $1000 on the account. Basicly the hacking behind this task is equal to task one, so will be sending in parallel request with Burpsuite intercept ON, we will turned OFF when we will receive a transaction-successfull
status back from the application server.
Let's transfer $95 dollars from Rasser to Zadovni, in the background i've prepared everything to intercept. Let's hit the transfer button and we'll capture the POST request in Burp.
Let's send the request into Repeater, Group Tab with a fancy name like "First Transfer" and duplicate 10 times. Before hitting Send button, set into parallel requests.
Turn intercept OFF and try to log in with Zadowni account. We will find a $400+ in balance, to transfer to Warunki.
Second transfer, before continuing make sure that everything in the background is intercepting fine our session!
QUICK TIPS! Try to not overdue when duplicating this second transaction, no more than 3 tabs and the magic will work fine ;)
Get the last flag THM{BANK-RED-FLAG} and our job is done here.
🧠 Personal Thoughts
What makes race conditions particularly interesting is their non-deterministic nature. Unlike SQL injection or XSS, which are often reliably reproducible, race conditions depend on timing. This makes them harder to detect, test, and fix.
From a developer’s perspective, it’s a reminder of the importance of atomic operations and locking mechanisms in concurrent environments. From a pentester’s view, it’s a thrilling challenge that requires creativity and precision.
🛡️ Mitigation Strategies
To prevent race conditions:
Use database transactions to ensure atomicity.
Implement locking mechanisms (e.g., mutexes) when accessing shared resources.
Validate state on the server side before and after critical operations.
Rate-limit sensitive actions to reduce the chance of concurrent abuse.
🧪 Final Thoughts
The TryHackMe Race Conditions room is a fantastic hands-on introduction to a subtle yet impactful vulnerability. It teaches not just how to exploit, but also how to think like an attacker—and more importantly, how to defend against such attacks.
If you’re into web security, this room is a must-try. It’s not just about hacking—it’s about understanding the systems we build and how they can fail in unexpected ways.