Skip to content

Services, Wiki-Artikel, Blog-Beiträge und Glossar-Einträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen
Schwachstellenklassen Glossary

Race Condition (TOCTOU) - Timing-basierte Sicherheitsschwachstelle

Race conditions (CWE-362) occur when a system's security depends on two or more operations being executed in a specific order, but parallel execution violates that order. TOCTOU (Time-Of-Check Time-Of-Use) is the most common form: checking and using a resource occur at different times. Security implications: double spending in financial applications, privilege escalation via temporary files, discount abuse, account takeover. Protection: atomic database operations, mutexes, optimistic locking.

Race Conditions are among the harder-to-find but often high-impact vulnerabilities. A classic example: An online banking application checks the account balance (€50) and then initiates a transfer. What if an attacker sends 100 transfer requests simultaneously? Each check shows €50, and each transfer is executed before the account balance is updated. Result: €5,000 debited instead of €50.

TOCTOU - Time Of Check / Time Of Use

Basic Principle

Normal (insecure) flow:

  • t=0: CHECK: Account balance = €50, transfer €50 → OK
  • t=1: ACTION: Debit €50, account balance = €0

Race Condition (parallel requests):

  • Request A, t=0: CHECK: Account balance = €50 → OK
  • Request B, t=1: CHECK: Account balance = €50 → OK (not yet updated!)
  • Request A, t=2: ACTION: Debit €50, account balance = €0
  • Request B, t=3: ACTION: Debit €50, account balance = -€50 (!)

> The time window between CHECK and USE = "Race Window". The larger the Race Window, the easier it is to exploit. Database calls, network requests, and I/O increase the window.

TOCTOU Categories

1. File System TOCTOU:

// Check if file exists:
if (!file_exists($filename)) {
    file_put_contents($filename, $data);  // ← Between check and use: symlink!
}
// Attacker creates a symlink between file_exists() and file_put_contents()
// Writes data to /etc/passwd or similar (if the process has root privileges)

2. Web Application TOCTOU (Business Logic):

  • Check if user has sufficient balance
  • Deduct balance
  • Race Window: send parallel HTTP requests

3. Operating System Level:

  • Processes with SUID bit: check file existence, then open
  • Between check and open: swap file
  • Privileged process now opens a different file than expected

Impact on Web Applications

1. Redeeming Gift Cards / Coupon Codes Multiple Times

Normal Process:

  1. Check: Is the coupon valid? (Status = unused)
  2. Apply discount
  3. Mark coupon as used

Race Condition:

  • Attacker sends 50 parallel requests with the same code
  • All check: Status = unused → all OK
  • All apply discount (before one sets "used")
  • Coupon redeemed 50 times!
Proof: Burp Suite Turbo Intruder
POST /apply-coupon { "code": "SAVE50" }
→ 50 parallel requests → check if credited multiple times

2. Double Spending

Crypto Wallet or E-Commerce:

  1. Check: Wallet balance ≥ 100
  2. Send transaction: -100
  3. Update wallet: -100

Race: send parallel transactions; both check 100 ≥ 100 → true. Both transactions are executed → effectively, 200 is spent from a 100 balance.

3. Limit Bypass (Rate Limiting via DB)

"User may create a maximum of 1 account":

  1. Check: Does the user already have an account? (COUNT(*) = 0)
  2. Create account
  3. Result: Account exists

Race: 100 parallel registration requests → all check: 0 accounts → true → all create accounts → User has 100 accounts!

4. TOCTOU during privilege change

Admin Panel: "Deactivate User Account":

  1. Check: Is the account active? (active = true)
  2. Invalidate sessions
  3. Set active = false

Race: User sends request at the same moment → Check: active = true → OK → Deactivation in progress → User request occurs between check and deactivation → still authorized!

Detection and Testing

Burp Suite Turbo Intruder

Specifically optimized for race condition testing: HTTP pipelining + simultaneous TCP connections, very precise timing (millisecond accuracy).

Basic Race Condition Test:

  1. Identify HTTP request (e.g., POST /apply-coupon)
  2. In Burp: Send to Turbo Intruder
  3. Payload: 50 requests with race=true
  4. Send simultaneously (not sequentially!)
  5. Analyze responses: more than 1 success?

Turbo Intruder Configuration:

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                          concurrentConnections=50,
                          pipeline=False)  # Separate connections!
    for i in range(50):
        engine.queue(target.req)  # Queue all immediately

def handleResponse(req, interesting):
    if '200' in req.status:
        table.add(req)  # Mark successful responses

HTTP/2 Single-Packet Attack:

  • All requests in a single TCP packet
  • Maximum synchronization → highest race window hit rate
  • Supported starting with Burp Suite 2022.9

Testing Steps for Business Logic

  • Gift Cards: Redeem the same code 20 times simultaneously
  • Bank Transfers: Transfer the same amount 10 times simultaneously
  • Registration: Create the same username 10 times simultaneously
  • Password Reset: Redeem the same token 5 times simultaneously
  • Discount: Apply the same code 20 times simultaneously

Indicators of Race Conditions

  • Multiple "success" responses to identical parallel requests
  • Different response times in parallel tests
  • Inconsistent database states after tests

Mitigation Measures

1. Database Level - Atomic Operations

-- Unsafe:
SELECT balance FROM accounts WHERE id = 123;  -- balance = 100
UPDATE accounts SET balance = balance - 50 WHERE id = 123;

-- Safe (atomic operation):
UPDATE accounts
SET balance = balance - 50
WHERE id = 123 AND balance >= 50;
-- Affected rows: 0 → Retry or error; Affected rows: 1 → Success!
-- SELECT FOR UPDATE (Pessimistic Locking):
BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE id = 123 FOR UPDATE;
-- Row is now locked! No other thread can modify it
UPDATE accounts SET balance = balance - 50 WHERE id = 123;
COMMIT;

2. Database Level - Unique Constraints

-- Prevents duplicate issuance of coupon codes:
CREATE TABLE coupon_redemptions (
    coupon_code VARCHAR(50) NOT NULL,
    user_id INT NOT NULL,
    UNIQUE (coupon_code, user_id)  -- DB prevents duplicates!
);
-- INSERT will fail if already exists → Race condition prevented!

3. Application Level - Mutex/Semaphore

import threading
lock = threading.Lock()

def transfer_money(from_account, to_account, amount):
    with lock:  # ← Exclusive access
        balance = get_balance(from_account)
        if balance >= amount:
            set_balance(from_account, balance - amount)
            set_balance(to_account, get_balance(to_account) + amount)
# Redis-based distributed locking (for scaled apps):
import redis
r = redis.Redis()
lock_key = f"transfer_lock_{account_id}"
if r.set(lock_key, "1", nx=True, ex=5):  # NX = only if it doesn't exist
    try:
        perform_transfer()
    finally:
        r.delete(lock_key)
else:
    raise Exception("Transfer already in progress, retry")

4. Idempotency Keys (for APIs)

Every critical action is assigned a unique key:

POST /api/transfer
Idempotency-Key: uuid-generated-by-client

Server-side:

  • Check: has this key already been processed?
  • Yes: return cached response (do not re-execute!)
  • No: process and store the key with the result

Prevents duplicate execution even during network retries. Standard in payment APIs (Stripe and PayPal use this pattern).

5. TOCTOU in the File System

// Use O_EXCL + O_CREAT (atomic creation):
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd == -1 && errno == EEXIST) {
    // File already exists → error
}
// O_EXCL: error if file already exists
// Atomic: no race condition between check and creation!