Table of Contents
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
- Daily API rate limits
- 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.
