BSCPServer-side
Race conditions
A race is a check-then-act gap. Two requests both pass a limit before either update lands.
14 min read · Academy topic: Race conditions
Objectives
- Describe a check-then-act window in one sentence
- Separate a wrong business rule from a right rule with a bad window
- Name the controls that make the check and the update one step
On this page
Core idea
A limit overrun is a time-of-check to time-of-use gap. The application reads a fact, decides, then writes. Between the read and the write the fact is still the old one. A second request that arrives in that window sees the same old fact and makes the same decision.
The usual story is a single-use code. Check whether it has been used, apply it, then mark it used. One request after another is fine: the second sees the mark. Two requests that overlap both see “unused” and both apply the code. The rule was right. The window was wrong. That is the difference from a business-logic flaw, where the rule itself is missing even for a single request.
The same window shows up as a gift card redeemed twice, a balance that goes below zero, a rating counted twice, a single-use challenge accepted twice, or a login limit that only counts after the attempt finishes. None of those need a special payload. They need two successes where the state should have allowed one.
What to look for
- A single-use token, code, or coupon
- A balance, quota, or inventory count read in one step and written in another
- A rate limit that increments after the protected action
- Two responses that both claim success, followed by a state that should have allowed only one
- A database update that is not tied to the check in the same transaction
In the lab / in Burp
Pick one single-use action the lab provides. Send it once and record the state afterward. That is the baseline.
Put that same request in a Repeater group and send the group together, the way the Academy exercise asks. Count how many responses claim success, then look at the state the limit was supposed to protect. Two successes mean the check and the update were separate steps.
Do not script a timing loop into this repo. The lab’s parallel send is the observation. Stay on the lab host.
Defensive controls
Make the check and the update one database step: a transaction, a row lock, or an atomic compare-and-set. An idempotency key gives each action a unique token the server rejects the second time it sees it. Sensitive operations can be queued so the second request waits until the first write is visible. A burst of identical requests from one session is a signal to log, not the fix. The fix is that the second request cannot pass the check.
Common pitfalls
Calling every double-submit a race. If one slow request would also break the rule, it is business logic. Calling a race “secure” because it is hard to hit by hand. Hoping users will click slowly. Treating a rate limit in the application as a lock when two workers both read the old count.
From the learning path
The February 2026 learning-path notes name this TOCTOU and list the single-use cases above. They also note that the window can be only a few milliseconds wide, so a careful manual click often misses it, and that a lab tool which delivers the group together is how you observe the collision. The defensive list in those notes is the same one in this page: atomic updates, locks, idempotency keys, and less trust that requests will arrive one at a time.