Race Condition - A cURL Chaos

Jerry Shah (Jerry)
9 min readNov 30, 2023

--

Summary

Race condition vulnerability occurs when websites process requests concurrently. Race conditions typically occurs in multi-threaded or multi-process environments where multiple threads or processes are competing for shared resources. A race condition attack depends on the relative timing of events and an attacker can manipulate this timing to cause unintended behavior in the application to perform some malicious tasks.

Description

I found a race condition vulnerability bypass on the private program of YesWeHack. The website had a basic plan and premium plan where in basic plan it was possible to invite only 99 users, so for testing of race condition I already invited 95 users manually and now only 4 invitations were left. So initially I used burpsuite to check for the vulnerability but it didn’t get trigger. After that I tried to trigger it using cURL command and it worked. After further analysis of the behavior the race condition that got triggered with cURL and not with BurpSuite, could be due to any of the below mentioned reasons.

Timing and Concurrency:

Burp Suite: The timing and concurrency settings in Burp Suite may not align with the conditions required to trigger the race condition. Burp Suite might send requests in a more controlled or sequential manner, mitigating this race condition.

Curl Command: The curl command might introduce the necessary concurrency or timing that exposes this vulnerability.

Request Headers and Structure:

Burp Suite: The requests generated by Burp Suite may have unique headers, ordering or characteristics that could be recognized by a server.

Curl Command: Curl requests may have different characteristics in terms of headers and structure compared to those generated by Burp Suite.

Network Characteristics:

Burp Suite: The way Burp Suite interacts with the network, especially if it is used as a proxy, may introduce specific characteristics that are detectable.

Curl Command: A direct curl command may not have the same network characteristics as requests routed through a proxy like Burp Suite.

Request Characteristics:

Burp Suite: The requests generated by Burp Suite might have certain characteristics, headers, or timing patterns that do not trigger the race condition. It is possible that Burp Suite’s default settings or the way it crafts requests may not reproduce the conditions necessary for the race condition to occur.

Curl Command: The curl command might be more effective at reproducing the specific conditions required to trigger the race condition. It could be sending requests in a way that aligns with the vulnerability, such as timing, order or headers.

Anatomy of Race Condition Vulnerability

Race conditions are often sensitive to timing and the success of an attack depends on precise timing manipulations. The period of time during which a collision is possible is known as the “race window”.

What is Race Window ?

Race Window refers to a time-frame during which a vulnerability can be exploited due to the concurrent execution of multiple threads or processes. It is the period in which the outcome of the program or system depends on the order and timing of events, leading to unexpected and insecure behavior.

Race Window

In the above image 5 requests were sent concurrently where the 1st three requests reached the race window within 6 milliseconds and the other 2 requests reached after 6 milliseconds which executed 1st three request concurrently triggering the race condition vulnerability.

Let’s consider that I had total of 5 invites out of which I used 4 and 1 is remaining on which I tried race condition attack.

Initial State:

1. I had a limit of 5 invitations.
2. I had already used 4 invitations and there was 1 pending invitation.
3. The system’s logic should have prevented me from sending more than 1 additional invitation.

Exploiting the Race Window:

I initiated a race condition attack, sending 5 concurrent requests to send invitations during a short time frame (6ms race window).

Race Condition Logic:

In a vulnerable system, the logic for checking and decrementing the number of available invitations was not be properly synchronized, allowing multiple threads or processes to execute this logic concurrently.

Incorrect Logic Execution:

Let’s assume that the logic for sending invitations looks something like this:

Invitation Logic

In a race condition, multiple threads will read the remaining_invitations value as greater than 0 before any of them has updated it.

NOTE: The -- operator in the line remaining_invitations-- ; is the decrement operator.

Concurrent Execution:

During the race window, all 5 concurrent requests read the remaining_invitations value as 1 (since only 1 invitation was pending initially) and all request thought they can send an invitation.

Unexpected Outcome:

The race condition causes all 5 threads to execute the logic to send an invitation and decrement remaining_invitations. Since the initial value was 1, they all decrement it to 0 concurrently.

Result of the Race Condition:

Instead of the expected behavior of sending only 1 invitation (since there was only 1 pending), the race condition allowed all 5 requests to execute the logic, resulting in 5 invitations being sent (3 more than intended).

Remaining Invitations and Failures:

After the race condition, the remaining_invitations value is now negative (-2), indicating that more invitations were sent than allowed.

The remaining 2 invitations that were attempted after the initial 1 pending invitation were likely denied or failed due to the logic checking for a negative remaining_invitations value.

Why 2 invitations request got failed ?

The failure of the remaining 2 invitations request could be due to various reasons and the race window time can be one of the factor. Here are a few possibilities:

Race Condition Window Exceeded:

If the race window is defined to be very short (e.g., 6ms) and the initial 5 concurrent requests took up most of that time, the remaining 2 invitations could have attempted after the defined race window had closed.

Synchronization or Locking Mechanism:

The system could have a synchronization or locking mechanism in place that prevents concurrent access to the critical section of code beyond the race window. Once the race window is closed, subsequent requests could be subject to proper synchronization.

Concurrency Controls:

The application could have controls in place to limit the number of concurrent operations beyond a certain threshold (race window time). If the remaining 2 invitations were attempted after the race window, they could have been subject to these controls.

How I found this vulnerability ?

  1. I went to target website invitation’s functionality where I already invited 95 users out of 99 and 4 invitations were pending
Invitation Functionality

2. I added a random email, right clicked on invite button > inspect element > network tab and clicked on Invite button to see the request

Adding Email
Inspect Element
Inspect Element - Network Tab

3. Then I right clicked on the invitation request > Copy Value > Copy as cURL (Windows), pasted it in a notepad and removed the ^ symbol from the request

Copy cURL Request - Windows
Removing ^ Symbol

4. Then I appended same invitation request eight times using & operator and entered different emails in all appended request

For e.g.

Example Request Concatenation
Requests Concatenation

5. I copied complete concatenated request of curl and ran it on my terminal

Executing Concatenated cURL Requests
Triggered Race Condition

NOTE: Out of 8 concurrent requests 6 got executed and 2 were failed.

Why this happened ?

In my opinion,

This happened because of the below mentioned factors.

Lack of Atomicity:

The critical operation of checking the remaining invitations and decrementing the count needs to be atomic (indivisible).

Without proper synchronization mechanisms (e.g., locks or atomic operations), the sequence of checking and updating the remaining invitations can be interleaved by multiple threads, leading to unexpected outcomes.

Sample Python Code - Responsible for Race Condition due to Lack of Atomicity
  1. The send_invitation function attempts to decrement the remaining_invitations variable without proper synchronization.
  2. The race_condition_worker function simulates multiple threads trying to send invitations concurrently.

Concurrent Execution:

Multiple threads or processes attempted to execute the invitation logic concurrently.

The system did not have adequate controls to ensure that the operations on the shared resource (remaining invitations) were mutually exclusive during the critical section of code.

Concurrent Execution
Sample Python Code - Responsible for Race Condition due to Concurrent Execution
  1. The send_invitation function simulates the logic for sending invitations, and it includes a lock (invitation_lock) to provide mutual exclusion.
  2. The concurrent_execution function represents the concurrent execution logic. Two threads (thread1 and thread2) are created, each calling this function to simulate concurrent invitation requests.
  3. The time.sleep(0.1) is added to simulate some processing time within the critical section.
  4. The final remaining invitations are printed after the concurrent execution.

Concurrency without Synchronization:

The absence of proper synchronization mechanisms meant that multiple threads could read and update the shared resource simultaneously.

The lack of coordination led to a race condition where the outcome depended on the order of execution of the concurrent operations.

Sample Python Code - Responsible for Race Condition due to Concurrency without Synchronization
  1. The send_invitation function attempts to decrement the remaining_invitations counter if it’s greater than 0.
  2. The concurrent_invitations function simulates a scenario where multiple threads concurrently attempt to send invitations.

Impact

Users can send more invitations than the allowed limit specified by their subscription plan (e.g., basic plan allowing 100 invitations) which can allow users to exploit the vulnerability to send a higher number of invitations that are meant to be restricted to premium-tier plans, affecting the business model and revenue generation.

CVSS

Vector String: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

Score: 3.7 Low

Mitigation

To mitigate race condition vulnerabilities following implementations can be taken into consideration.

Synchronization Mechanisms

Implementing proper synchronization mechanisms, such as locks or semaphores to control access to shared resources, including the code responsible for processing invitation requests. Synchronization ensures that only one thread or process can access the critical section of code at a time, preventing race conditions and ensuring proper handling of the invitation limit.

However some synchronization technique uses a thread locking mechanism which can sometimes lead to performance issues of the application and according to my opinion atomic operations are better mitigation than synchronization mechanisms.

Atomic Operations

Use atomic operations or transactional approaches when updating shared variables or resources to ensure that the operations are executed as a single, indivisible unit. Atomic operations help prevent interleaved execution of multiple threads, reducing the likelihood of race conditions.

Atomic operations are a type of low-level synchronization mechanism which ensures that a process’s operations are performed in a single, indivisible step. It prevents other processes from interfering with the resource while the operation is being performed. This mitigation will not affect the performance of the application.

Sample Java Code:

import java.util.concurrent.atomic.AtomicInteger;

public class RaceConditionMitigation {
private static AtomicInteger counter = new AtomicInteger(0);

public static void main(String[] args) {
// Create multiple threads to increment the counter concurrently
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++)

{ counter.incrementAndGet(); // Atomic increment operation }

});
threads[i].start();
}

// Wait for all threads to finish
for (Thread thread : threads) {
try

{ thread.join(); }

catch (InterruptedException e)

{ e.printStackTrace(); }

}

// Print the final counter value
System.out.println(“Counter: “ + counter.get());
}
}


In this example, the “counter” variable of type “AtomicInteger” provides atomic operations. Each thread increments the counter by invoking the “incrementAndGet()” method, which guarantees atomicity and thread-safety.

By using “AtomicInteger”, we can ensure that the increment operation is executed atomically, preventing race conditions and ensuring the correct result. The final value of the counter will be the sum of all increments performed by the threads.

Note that the “AtomicInteger” class also provides other atomic operations, such as “decrementAndGet()”, “getAndIncrement()” and “getAndSet()” which can be used depending on the specific requirements of your application.

Remember to import the “java.util.concurrent.atomic.AtomicInteger” class to use atomic operations in your code.

--

--

Jerry Shah (Jerry)

|Penetration Tester| |Hack The Box| |Digital Forensics| |Malware Analysis|