BBC Ch 12: Race Conditions
Source: Bug Bounty Bootcamp by Vickie Li
How Race Conditions Work
A race condition occurs when the security of a system depends on the sequence or timing of events, and that sequence can be disrupted by an attacker. Web applications are particularly vulnerable when they perform a check-then-act sequence without atomic locking: the state can change between the check and the act.
The classic pattern is time-of-check/time-of-use (TOCTOU):
Application reads current state (check)
Application performs action based on that state (use)
If two requests execute concurrently, both may pass the check before either completes the use, leading to double-execution
Example: Bank Transfer Double-Spend
Account has $500. Transfer $500 to account A, and simultaneously (before the first transfer completes) transfer $500 to account B:
Thread 1: checks balance → $500, OK to proceed
Thread 2: checks balance → $500 (first transfer not yet committed), OK to proceed
Thread 1: deducts $500, sends to account A
Thread 2: deducts $500, sends to account B
Balance is now -$500; $1000 was sent from a $500 account
Example: Coupon/Vote Manipulation
An application allows a coupon code to be applied once. If the “check used” and “mark used” steps are not atomic:
Thread 1: checks – not used → OK
Thread 2: checks – not used → OK (mark not yet written)
Thread 1: applies discount, marks used
Thread 2: applies discount, marks used
Both threads succeed and the coupon is applied twice. The same logic applies to vote-once checks, daily limit checks, and inventory reservation.
Prevention
Atomic database operations – use transactions with proper isolation levels so that check and update happen as a single atomic unit
Resource locks – acquire a lock on the resource before checking; release after the update; only one thread can hold the lock at a time
Synchronization – language/framework primitives (mutexes, semaphores) to serialize access to the critical section
Principle of least privilege – limit per-account transfer rates, vote limits enforced at the DB constraint level
Hunting for Race Conditions
Step 1: Find Features with Limits
Target any functionality that:
Enforces a numeric limit (one vote, one coupon use, $X transfer limit)
Reads a value then writes a new value in separate operations
Has a critical action gated on a prior state check
Examples:
Gift card redemption (single use)
Promotional discount codes
Transfer/withdrawal with a balance check
-
Poll voting (one vote per user)
Email/SMS confirmation codes (can be used exactly once)
Step 2: Send Simultaneous Requests
Use curl with the & operator to fire multiple requests in parallel from the shell:
curl -s "https://example.com/api/transfer?to=attacker&amount=500" &
curl -s "https://example.com/api/transfer?to=attacker&amount=500" &
curl -s "https://example.com/api/transfer?to=attacker&amount=500" &
wait
For POST requests with a session cookie:
for i in $(seq 1 10); do
curl -s -X POST https://example.com/api/use_coupon -d "code=SAVE50" -H "Cookie: session=YOUR_SESSION" &
done
wait
The & sends each request to the background without waiting for the previous to finish.
Step 3: Check for Impact
After the parallel requests complete, check whether the limit was exceeded:
Was the coupon applied multiple times? Check order history.
Was the vote counted multiple times? Check vote totals.
Did the balance go negative? Check account balance.
Were multiple confirmation codes consumed? Check server-side state.
Step 4: Vary Timing and Thread Count
Race conditions are timing-sensitive. If the first attempt fails, try:
More simultaneous requests (10, 50, 100)
Requests spread across a slightly longer window
Burp Suite's Turbo Intruder extension (designed for high-concurrency HTTP races)
Python with threading or asyncio for more precise control
Escalating the Attack
Financial double-spend – transfer same balance to attacker account twice; redeem gift cards multiple times; use discount codes repeatedly
Privilege escalation – if admin creation or role assignment is gated on a check, race to create two admin accounts simultaneously
Bypass rate limits – if an OTP or password-reset code can be verified multiple times before expiry, race to try more guesses than allowed
Inventory/resource exhaustion – over-claim limited seats, beta invites, or limited-quantity items
5-Step Checklist
Find features that enforce a one-time or limited-count action (coupons, votes, transfers, OTPs).
Understand the check-then-act sequence; identify what window exists between check and commit.
Send simultaneous curl requests (or Turbo Intruder) targeting the limited action.
Check state after requests complete to confirm the limit was exceeded.
Quantify impact: financial loss, unauthorized access, or data corruption and document with a minimal PoC.