You are here:

What are race conditions?

Race condition attacks exploit timing issues in apps, risking data corruption and breaches. Learn how to prevent them and keep your applications secure.

Today, many apps handle lots of data at the same time. It’s really important to make sure these tasks are safe and secure. One big problem that developers face is called a “race condition attack.” These attacks happen when the timing of actions in an app isn’t managed properly, which can cause problems like messing up data, letting unauthorized people in, or crashing the system. In this blog post, we’ll talk more about race condition attacks and share ways to stop them from happening, so your app stays safe from bad actors. 

 

 

Understanding Race Condition

 

Race conditions happen when two different threads or processes try to access and change the same data at the same time. This can lead to inconsistencies in the data stored in the database. 

 

Let’s look at a common example to understand this better. Imagine we have an online shop where users can buy items, and the website keeps track of how many of each item are in stock using a database. 

 

User A and User B both want to buy the last RTX 4090 graphics card available on the website at the same time. Their requests to buy the card are sent to the database simultaneously. The database receives both requests at the same time and tries to update the quantity of the graphics card to show that it’s sold out. However, because both requests happened at the same time, the database might deduct the quantity twice, resulting in a negative quantity (-1). This means that both users might think they’ve bought the card, but in reality, there’s only one available. This could harm the company’s reputation and make customers lose trust in the website. 

 

This is just one example of how race conditions can cause problems. For instance, if User A and User B try to register with the same username at the same time, they might both end up with accounts using the same username, causing confusion. Similarly, if both users try to use a coupon that can only be used once at the same time, they might both receive the discount when only one should have. 

 

Race conditions can lead to unexpected behavior in applications and can be difficult to detect and fix. It’s important for developers to be aware of them and implement strategies to prevent them from happening. 

 

 

Limit overrun race conditions 

 

A common type of race condition involves surpassing a limit set by the rules of a program. Let’s take the example of an online shop. Imagine you can input a special code when buying something to get a discount. Here’s how it works: 

 

First, the program checks if you’ve already used this code. 

 

If not, it applies the discount to your purchase. 

 

Then, it updates its records to show that you’ve used the code. If you try to use the same code again later, the initial checks should stop you from doing so. 

 

 

 

 

Now, think about what might occur if a user who hasn’t used this discount code before tries to use it twice, almost simultaneously

 

 

 

 

In this scenario, the application goes through a temporary stage, called a sub-state. It enters this stage when it starts handling the first request and exits it when it updates the database to show that you’ve used the code. During this time, there’s a brief period where you can try to claim the discount multiple times before the process is finished. 

 

 

Finding and exploiting race conditions that exceed limits with Turbo Intruder 

 

Aside from supporting single-packet attacks in Burp Repeater, we’ve upgraded the Turbo Intruder extension to handle this method too. You can get the latest version from the BApp Store. 

 

Turbo Intruder needs some Python knowledge but is great for complex attacks like those needing multiple retries, timed requests, or many requests. 

 

To use the single-packet attack in Turbo Intruder: 

 

Make sure the target uses HTTP/2; this attack doesn’t work with HTTP/1. 

 

Set the engine=Engine.BURP2 and concurrentConnections=1 options. 

 

When queuing requests, group them by assigning them to a named gate using the gate argument for the engine.queue() method. 

 

To send all requests in a group, open the respective gate with engine.openGate(). 

 

Here’s an example: 

 

def queueRequests(target, wordlists): 

 

engine = RequestEngine(endpoint=target.endpoint, 

 

                            concurrentConnections=1, 

 

                            engine=Engine.BURP2 

 

                            ) 

 

    # queue 20 requests in gate ‘1’ 

 

    for i in range(20): 

 

        engine.queue(target.req, gate=’1′) 

 

     

 

    # send all requests in gate ‘1’ in parallel 

 

    engine.openGate(‘1’) 

 

For more, check the race-single-packet-attack.py template in Turbo Intruder’s default examples. 

 

In real-world scenarios, one request might trigger a multi-step sequence, moving the application through hidden states before finishing. We call these “sub-states”. 

 

If you find HTTP requests interacting with the same data, you could exploit these sub-states to expose time-sensitive flaws common in multi-step processes. This can lead to race condition exploits beyond just exceeding limits. 

 

For instance, flawed multi-factor authentication (MFA) might let you start login with known credentials and bypass MFA entirely. 

 

 

Multi-endpoint race conditions 

 

One of the easiest-to-understand types of these race conditions involves sending requests to multiple endpoints simultaneously. 

 

Consider the common logic flaw in online stores: You add an item to your cart, pay for it, then add more items to the cart before going to the order confirmation page. 

 

A similar vulnerability arises when payment validation and order confirmation happen together in one request. The order status state machine might resemble this: 

 

 

 

 

In this situation, there’s a chance to add additional items to your basket during the brief period between when the payment gets validated and when the order is ultimately confirmed. 

 

 

Single-endpoint race conditions 

 

When you send multiple requests simultaneously to a single endpoint with different values, it can sometimes lead to significant race conditions. 

 

Take, for example, a password reset system that saves the user ID and reset token in the user’s session. 

 

In this case, if you send two password reset requests at the same time from the same session but with two different usernames, it could result in the following issue: 

 

 

 

 

Now, the session holds the victim’s user ID, but the valid reset token is sent to the attacker, achieving the desired outcome. 

 

Email address confirmations, along with other email-based operations, are prime targets for single-endpoint race conditions. This is because emails are frequently sent in a background thread after the server responds to the client’s HTTP request, increasing the likelihood of race conditions. 

 

 

Session-based locking mechanisms 

 

Certain frameworks employ session-based locking mechanisms to mitigate unintentional data corruption. For instance, PHP’s native session handler module restricts processing to one request per session at a time. 

 

Identifying this behavior is crucial as it might obscure easily exploitable vulnerabilities. If you observe that all your requests are handled sequentially, attempt sending each request using a distinct session token. This can help reveal potential issues that might otherwise remain hidden. 

 

 

Partial construction race conditions 

 

Many applications follow a multi-step process to create objects, which can introduce a temporary intermediate state where the object is vulnerable. 

 

For instance, during the registration of a new user, an application might execute two separate SQL statements to create the user in the database and set their API key. This creates a small window of time where the user exists, but their API key is not yet initialized. 

 

This behavior opens the door to exploits where you manipulate input values to match the uninitialized database value, such as an empty string or null in JSON. These manipulated values might be compared as part of a security check. 

 

Frameworks often provide ways to pass non-string data structures, like arrays, using unconventional syntax. 

 

 

Time-sensitive attacks 

 

Even if you don’t discover race conditions, employing techniques to send requests with exact timing can uncover other vulnerabilities. 

 

For instance, using high-resolution timestamps instead of cryptographically secure random strings to generate security tokens can pose a risk. Let’s take the example of a password reset token that relies solely on a timestamp for randomization. It becomes feasible to initiate two password resets for different users, resulting in both users sharing the same token. Achieving this only requires timing the requests to generate identical timestamps. 

 

 

How to prevent race condition vulnerabilities? 

 

Understanding and predicting the behavior of an application that undergoes invisible sub-states with a single request is incredibly challenging, making defense impractical. To ensure proper application security, we suggest eliminating sub-states from all sensitive endpoints by implementing the following strategies: 

 

 

  • Avoid mixing data from different storage places:
    Keep data segregated to prevent unintended interactions.
     
  • Ensure sensitive endpoints maintain atomic state changes:
    Use the concurrency features of your datastore to ensure that operations such as checking payments against cart values and confirming orders occur as a single, indivisible transaction.
     
  • Leverage datastore integrity and consistency features:
    Utilize features like column uniqueness constraints to enhance the security and reliability of your data.
     
  • Do not rely on one data storage layer to secure another:
    For instance, using sessions to prevent database attacks like limit overruns is not advisable.
     
  • Ensure consistency in session handling frameworks:
    Update session variables as a whole rather than individually to maintain internal consistency. Similarly, Object-Relational Mapping (ORM) frameworks should handle transactions transparently to avoid errors.
     
  • Consider avoiding server-side state:
    In some architectures, storing state client-side using encryption, such as with JSON Web Tokens (JWTs), might be more suitable. However, be aware that this approach introduces its own security risks, which should be carefully considered. 

 

 

By implementing these strategies, you can enhance the security posture of your application and mitigate the risks associated with sub-states.

 

By understanding how these attacks work and the steps you can take to prevent them, you can safeguard your applications and user data. If you’re looking for additional help with application security or penetration testing, ValueMentor can assist. With our team of experienced professionals, we can help you identify and address vulnerabilities in your applications.

 

 

FAQs

 


1. Can JavaScript have race conditions? 

Yes, JavaScript can have race conditions, especially in asynchronous code where multiple operations run in parallel using Promises, setTimeout, or async/await. Improper handling of shared data or uncontrolled execution flow leads to unexpected outcomes. 


2. What causes race conditions? 

Race conditions occur when two or more operations access shared resources concurrently, and the program’s behavior depends on the order in which these operations execute. Lack of proper synchronization, locking, or validation often causes this issue. 


3. What are race conditions in programming? 

In programming, a race condition is a logic flaw that happens when the timing or sequence of threads or processes leads to incorrect results. It’s common in multithreaded and asynchronous applications that share state or resources. 


4. How to handle race conditions? 

Handling race conditions involves using synchronization techniques like mutexes, semaphores, locks, atomic operations, or condition variables. Structuring code to ensure consistent access and modification of shared resources is key.


5. How to test race conditions? 

Race conditions are tested using stress testing, fuzzing, code reviews, and concurrency testing tools. Dynamic analysis, thread sanitizer tools, and deliberately inducing thread scheduling issues can help expose timing-related flaws. 


6. How to prevent race conditions? 

To prevent race conditions, implement thread-safe coding practices, avoid shared mutable state, use synchronized methods or atomic data types, and ensure predictable execution order in concurrent environments. 


7. How to debug race condition? 

Debugging a race condition involves reproducing the issue under different timing conditions, using concurrency debugging tools, reviewing log traces, and isolating non-deterministic behavior caused by overlapping operations. 


8. What are race conditions in software? 

Race conditions in software are logic errors triggered by unsynchronized access to shared resources, which can lead to inconsistent data, security vulnerabilities, or system crashes depending on the execution timing. 


9. What are race conditions in Java? 

In Java, race conditions occur when multiple threads access and modify the same variable simultaneously without proper synchronization. Java provides thread-safe constructs like synchronized blocks, ReentrantLock, and Atomic* classes to manage this risk. 


10. What are race conditions in OS (Operating System)? 

In operating systems, race conditions can affect kernel modules, file handling, or process scheduling. They arise when concurrent system-level tasks interfere with each other, leading to data corruption, privilege escalation, or unexpected behavior.

Table of Contents

Protect Your Business from Cyber Threats Today!

Safeguard your business with tailored cybersecurity solutions. Contact us now for a free consultation and ensure a secure digital future!

Ready to Secure Your Future?

We partner with ambitious leaders who shape the future, not just react to it. Let’s achieve extraordinary outcomes together.

I want to talk to your experts in:

Related Blogs

Illustration of cybersecurity professionals analyzing data on large digital screens, symbolizing the importance of PCI penetration testing for protecting business systems and sensitive information