Skip to content

Race Conditions

A race condition happens when two or more requests touch the same state at once and the result depends on the exact order the server processes them. Between the moment the app checks something (balance, coupon, stock, limit) and the moment it acts on it, there is a window —sometimes milliseconds— in which several requests see the “old” state and all pass the check. It’s the classic TOCTOU (Time-Of-Check to Time-Of-Use).

The attacker doesn’t break the logic: they run it many times simultaneously before the state updates. If “redeem coupon” checks used == false then sets used = true, sending 50 redemptions in parallel can slip 50 through before the first one writes. The damage is directly business impact: money, stock, limits, uniqueness.

Look for operations where “once” or “only N times” matters:

  • Redeem coupon/gift card, apply a one-time discount.
  • Withdraw balance / transfer (spend the same money twice).
  • Attempt limits (rate-limit bypass), vote/like limits.
  • Reserve unique stock/seat; register a unique username/email.
  • Privilege escalation in a two-step flow.

The goal is to make all requests land in the same window. Two key methods:

  • Single-packet attack (HTTP/2): ~20-30 requests sent in a single TCP packet, removing network jitter → they arrive practically simultaneously. The modern reference technique (James Kettle).
  • Last-byte sync (HTTP/1.1): send all requests minus the last byte, then that last byte of all of them at once.

With Burp Repeater just group the tabs and use “Send group in parallel”. With Turbo Intruder, the race-single-packet-attack.py script.

# conceptual pattern
for i in 1..30: prepare POST /redeem (coupon=X) # without sending the last byte
send the last byte of all 30 at once
-> if several return "redeemed successfully", there is a race
  • Limit-overrun: spend a single-use resource several times (the balance/coupon case).
  • Multi-endpoint: two different endpoints touching the same object in parallel (e.g. apply coupon while confirming payment).
  • Single-endpoint state collision: two updates to the same record stepping on each other.
  • Partial construction: using a half-created object before its invariants are validated.
  • Burp Suite — Repeater with parallel groups (single-packet built in).
  • Turbo Intruder — single-packet / last-byte sync scripts.
  • ffuf/curl + GNU parallel — for simple HTTP/1.1 cases.

Free money (double spend, unlimited coupons), bypass business limits, break uniqueness (two accounts with the same email), privilege escalation.

  • Multiple identical requests to the same resource in a millisecond window from the same user.
  • Inconsistent states: negative balance, a coupon with N uses when max was 1.
  • Concurrency spikes on a single record.

Log with high-resolution timestamps and a request ID; correlate concurrent requests on the same object.

  • Database atomicity: transactions with the right isolation level; SELECT ... FOR UPDATE (pessimistic lock) or a version/WHERE used=false in the UPDATE (optimistic) so only one wins.
  • Locks at the application/record level (idempotency keys, per-user/resource locks).
  • Unique constraints in the database itself (unique index) that fail the second write.
  • Idempotent operations with an idempotency token for payments.
  • Don’t separate check and action: do it in a single atomic operation.

Reconcile state (void double redemptions/spends), add the missing lock, review logs for historical exploitation of the same pattern.

  • James Kettle — “Smashing the state machine” (PortSwigger, 2023) — introduces the single-packet attack; the basis of modern web race exploitation.
  • E-commerce / fintech platforms — many HackerOne reports of coupons and balances redeemed multiple times.
  • Starbucks, points programs — public cases of balance duplication via race.
  • GitLab / Shopify — publicly reported race bugs in invitations and limits.
  • Are there “single-use” or “limit N” operations? (coupon, balance, votes)
  • Does sending 20-30 parallel requests (single-packet) slip more than one through?
  • Can the same resource be spent twice? (limit-overrun)
  • Do two endpoints touch the same object with no common lock?
  • Is there a unique DB constraint or only a code check?
  • Are check and action a single atomic transaction?
  • Are there idempotency keys on payments?