SecureBlockLog inStart a pentest
Vulnerability Repository
HighAccess Control

Race Condition (TOCTOU)

Race conditions exploit the window between checking and using a resource, allowing attackers to double-spend, bypass limits, or escalate privileges through concurrent requests.

CVSS 7.5CWE CWE-362OWASP A04:2021 — Insecure Design

Description

A race condition (Time-of-Check to Time-of-Use, or TOCTOU) vulnerability occurs when an application checks a condition, then performs an action based on that check, and the state can change between the check and the use. In web applications, these vulnerabilities arise when concurrent requests exploit the gap between a validation step and the corresponding state change—allowing attackers to perform actions that should only be possible once, bypass single-use restrictions, or violate business logic invariants.

CWE-362 (Concurrent Execution Using Shared Resource with Improper Synchronization) describes the root cause: shared state is accessed by multiple execution paths without proper locking or atomic operations. A04:2021 Insecure Design captures the architectural nature of the fix—race conditions cannot be patched by adding input validation; they require redesigning the check-then-act pattern using atomic database operations, distributed locks, or idempotency keys.

Common web application race condition targets include: redeeming a coupon or voucher (check balance → deduct → charge), promotional code use limits, voting systems (one vote per user), file upload processing, cryptocurrency or financial transfer operations, and subscription or license validation checks.

How It Works

A coupon redemption endpoint checks whether a coupon has been used before applying it:

# Vulnerable implementation
def redeem_coupon(user_id, coupon_code):
    coupon = db.query("SELECT * FROM coupons WHERE code = %s", coupon_code)
    
    if coupon.used:                          # CHECK: is coupon unused?
        return error("Coupon already used")
    
    time.sleep(0.01)                         # Simulates DB processing time
    
    db.execute("UPDATE coupons SET used=1 WHERE code = %s", coupon_code)  # USE
    apply_discount(user_id, coupon.value)

An attacker sends 20 concurrent requests to redeem the same coupon:

# Turbo Intruder script — send 20 simultaneous requests
# In Burp Suite: right-click request → Send to Turbo Intruder
# Use the "race-single-packet" template

# Or with curl parallel:
for i in $(seq 1 20); do
  curl -s -X POST https://api.example.com/coupon/redeem \
    -H "Authorization: Bearer <token>" \
    -d '{"code":"SAVE50"}' &
done
wait

Because all 20 requests read coupon.used = 0 (False) before any of them have written the update, all 20 proceed past the check and apply the discount—a single coupon is redeemed 20 times.

Burp Suite's Turbo Intruder extension with the single-packet attack technique synchronizes multiple requests to arrive at the server within a single TCP segment, maximizing the overlap of the check/use race window:

# Turbo Intruder race condition template
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=1,
                           requestsPerConnection=20,
                           pipeline=True)
    for i in range(20):
        engine.queue(target.req, str(i))
    engine.start(timeout=10)

A password reset token reuse race allows two attacker sessions to consume the same single-use token simultaneously, both gaining access.

Impact

  • Double-spending — Single-use coupons, vouchers, gift cards, or withdrawal limits are bypassed by concurrent exploitation, multiplying their value.
  • Privilege escalation — Concurrent requests to an account upgrade flow result in elevated privileges being applied multiple times or to unintended accounts.
  • Vote/rating manipulation — Per-user voting limits are bypassed, corrupting poll results or review systems.
  • Inventory overselling — Race conditions in inventory checks allow more purchases than stock available, creating negative inventory.
  • Authentication bypass — Single-use password reset tokens or MFA codes are consumed by concurrent requests, with one succeeding authentication and another being incorrectly invalidated.

Detection

  1. Identify all single-use or limited-use endpoints: coupon redemption, vote submission, password reset, OTP verification, referral bonuses, and similar features.
  2. Use Burp Suite's Turbo Intruder extension with the single-packet attack template to send 10-20 simultaneous requests to each candidate endpoint and observe whether multiple requests succeed where only one should.
  3. Monitor response bodies across concurrent requests: if multiple requests return a "success" response for a single-use operation, the race condition is confirmed.
  4. Test using curl with background processes (&) or ffuf with high concurrency against time-sensitive check-then-act operations.
  5. Review application code for the pattern: SELECT followed by conditional logic followed by UPDATE without wrapping the entire sequence in a transaction with appropriate isolation level or using SELECT FOR UPDATE.

Remediation

Use atomic database operations. Replace check-then-act patterns with atomic conditional updates:

-- Atomic update: only succeeds if used=0, sets used=1 in one operation
UPDATE coupons SET used=1 WHERE code = 'SAVE50' AND used=0;
-- Check affected rows: 0 means already used, 1 means success

Use database transactions with appropriate isolation. Wrap multi-step operations in serializable transactions with row-level locking:

with db.transaction(isolation='SERIALIZABLE'):
    coupon = db.query("SELECT * FROM coupons WHERE code = %s FOR UPDATE", code)
    if coupon.used:
        raise Exception("Already used")
    db.execute("UPDATE coupons SET used=1 WHERE code = %s", code)

Implement idempotency keys. For financial operations, require clients to supply a unique idempotency key; the server stores the key and returns the cached result for duplicate requests.

Use distributed locks for non-database resources. Redis SET NX PX provides atomic check-and-set locking for race-sensitive operations that span multiple services.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping