Race Conditions
You fire the same request many times in the same instant and slip through the gap between check and use.
What you will learn
- -Explain the TOCTOU window that makes limit checks fail
- -Recognize limit-overrun targets like coupons and withdrawals
- -Fire near-simultaneous requests with the single-packet technique
A gift-card balance of 10 dollars should redeem once. Send fifty redeem requests in the same 5 millisecond window and several of them read the balance before any of them writes the new one, so the card pays out five or six times. That gap between when the app checks a value and when it updates it is the whole .
The pattern has a name, TOCTOU, time-of-check to time-of-use. The app checks a condition (balance is 10, coupon unused, one vote per user) and then acts on it. If two requests both pass the check before either finishes acting, they both act, and the invariant the check was supposed to protect is gone. Single requests never expose it. You need .
What to aim at
Anything that enforces a limit by reading state, deciding, then writing state is a candidate. Look for the "you can only do this once" or "up to N" features, because those are exactly the checks a race defeats.
$POST /coupon/apply code=SAVE20$POST /withdraw amount=100$POST /invite/redeem token=abc$POST /vote option=2Gift cards and store credit are the cleanest demonstrations because the overrun converts directly to money. But the same bug shows up in rate limits, in "reserve the last seat" flows, and in any registration that checks a username is free before inserting it.
The window is tiny, so timing is everything
The race window can be a few milliseconds. Requests sent one after another over separate connections arrive too far apart and each one sees the previous one's write. You have to make them land together.
The old way was to queue many requests and release them at once. The sharp way now is the single- attack, which removes jitter as a variable entirely by putting the tail end of many /2 requests into one packet. All of them complete their first, then the final frames release together, so the starts processing them within microseconds of each other.
$Burp Repeater group > Send group in parallel (single-packet attack)Winning a race can mint currency, drain balances, or corrupt shared records, and the app has no clean way to know it was you testing. Run these only against systems you own or have written for. PortSwigger's Web Security Academy has a full race conditions track built for the single-packet technique, and it resets cleanly between attempts.
Confirm and measure
Send the burst, then read the result state and compare it against the limit. If a 10 dollar card shows 60 dollars redeemed, or a single-use coupon appears on the order three times, the race landed. Note how many of the parallel requests succeeded, because that number tells you how wide the window is and whether the fix later actually closed it.
$seed a card with a known small balance$group 30 identical redeem requests in Repeater$send group in parallel$read final balance and redemption countThe fix side is worth knowing so you can describe it in a report. The durable patch is a database-level lock or an atomic conditional update, UPDATE cards SET balance = balance - 10 WHERE id = ? AND balance >= 10, so the check and the write happen as one indivisible operation. Application-level checks in separate statements will always have the gap.
What is the name of the gap between when an app checks a value and when it acts on it, the gap a race condition exploits?
Why does the single-packet attack beat the old queue-and-release method for triggering races?