SecureBlockLog inStart a pentest
Vulnerability Repository
HighAccess Control

Business Logic Race Condition

Concurrent requests exploit time-of-check to time-of-use gaps in business logic, enabling double-spending, coupon reuse, balance manipulation, and limit bypass attacks.

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

Description

Business logic race conditions exploit the time gap between when an application checks a condition (time-of-check) and when it performs the corresponding action (time-of-use). In this window, a concurrent request from the same or a different session can change the state that the check was validating, causing the action to proceed under conditions that the check was designed to prevent.

CWE-362 — Concurrent Execution using Shared Resource with Improper Synchronisation covers this class. Unlike classic race conditions in multi-threaded systems, business logic race conditions in web applications often involve multiple HTTP requests to stateless application tiers that share a database — making them particularly prevalent in e-commerce, financial, and subscription systems.

Under A04:2021 — Insecure Design, OWASP classifies this as a design failure: the application was designed to enforce a business rule (use a coupon once, redeem a voucher once, allow one promotional discount) but the implementation does not account for concurrent requests that arrive simultaneously before any single request has committed its limiting state.

The attack was brought to wide attention through bug bounty reports demonstrating double-spending on cryptocurrency exchanges, free tier upgrade bypass on SaaS platforms, and infinite gift card credit generation.

How It Works

Single-use coupon race condition:

Normal flow:
1. Check: SELECT used FROM coupons WHERE code='SAVE50' → used=0
2. Apply: $50 discount applied to cart
3. Mark: UPDATE coupons SET used=1 WHERE code='SAVE50'

Race attack (two requests arrive simultaneously before step 3):
Request A: Step 1 → used=0 ✓
Request B: Step 1 → used=0 ✓   (step 3 hasn't executed yet)
Request A: Step 2 → $50 discount applied
Request B: Step 2 → $50 discount applied
Request A: Step 3 → used=1
Request B: Step 3 → used=1 (already 1, no change — no error)

Result: $100 discount obtained with a single-use coupon

This is the classic TOCTOU pattern. The gap between steps 1 and 3 is the exploitation window.

Using Burp Suite's parallel requests feature for race condition testing:

# Burp Repeater → Group → Send group in parallel (Last-Byte Sync)
# All requests are prepared and their final bytes are sent simultaneously
# to minimise the timing gap between arrival at the server

James Kettle's research on "Smashing the State Machine" (PortSwigger, 2023) introduced the concept of single-packet attacks in HTTP/2, where multiple requests can be sent in a single TCP packet, ensuring they arrive at the server simultaneously and eliminating network jitter as a timing variable.

Balance manipulation via parallel withdrawals:

# Attacker script: fire 10 withdrawal requests simultaneously
import asyncio, aiohttp

async def withdraw(session):
    await session.post('/api/withdraw', json={"amount": 100})

async def attack():
    async with aiohttp.ClientSession() as session:
        tasks = [withdraw(session) for _ in range(10)]
        await asyncio.gather(*tasks)

asyncio.run(attack())
# If the check (balance >= 100) passes for all 10 before any commit,
# 10 × $100 may be withdrawn from a $100 balance

Impact

  • Financial loss — double-spending attacks on gift cards, referral credits, and promotional balances directly drain application revenue.
  • Unlimited plan escalation — parallel requests to upgrade a free account can bypass one-time promotional pricing or feature limits.
  • Inventory exploitation — simultaneous purchase requests for limited-stock items can allow multiple users to buy quantities that exceed available stock.
  • Authentication bypass — race conditions in multi-factor authentication flows can allow a second authentication session to proceed before the first OTP is consumed.
  • Reputation damage — publicised race conditions that allow financial exploitation undermine customer trust in the platform's reliability and security.

Detection

  1. Identify single-use or limited-use business logic — map all application features that enforce one-time or limited-action rules: coupon codes, referral rewards, free trial activations, password reset links, OTP consumption.
  2. Use Burp Suite's parallel request feature — group the redemption request in Burp Repeater and use "Send group in parallel (Last-Byte Sync)" to fire 20-50 simultaneous copies.
  3. Test with HTTP/2 single-packet attacks — if the target supports HTTP/2, use Burp's single-packet attack to send multiple requests in one TCP frame, eliminating jitter.
  4. Check for non-atomic check-then-act patterns — in code review, look for any pattern where a read operation (SELECT, cache.get) precedes a write operation (UPDATE, INSERT) without database-level locking.
  5. Monitor for duplicate outcomes — after parallel testing, check whether the limiting state was applied correctly (only one coupon use recorded) or whether multiple successful outcomes were observed.

Remediation

Use database-level atomic operations. Replace check-then-act patterns with a single atomic database operation:

-- Non-atomic (vulnerable)
SELECT used FROM coupons WHERE code=? AND used=0;
UPDATE coupons SET used=1 WHERE code=?;

-- Atomic (safe) — UPDATE with WHERE condition acts as check + mark
UPDATE coupons SET used=1 WHERE code=? AND used=0;
-- Check affected rows: if 0, the coupon was already used

Use optimistic locking with version fields. Add a version column and include it in UPDATE conditions. If the version has changed between read and write, the UPDATE affects 0 rows and the transaction fails:

UPDATE coupons SET used=1, version=version+1 WHERE code=? AND version=?;

Use database row-level locking (SELECT … FOR UPDATE). This acquires a write lock on the selected row, preventing other transactions from reading it until the current transaction commits.

Apply idempotency keys. For financial transactions, require clients to provide a unique idempotency key per operation and reject or deduplicate requests with a previously-seen key.

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