caon.io

Race Conditions

Anything that reads state, decides, then writes state has a window. Hit it with two requests before the write lands.

SEQUENTIAL — jitter separates the requests check → write → check → write state is consistent, coupon redeems once SINGLE PACKET — 20 requests, one TCP frame check · check · check … then write · write · write all 20 pass the check — coupon redeems 20× the window is the gap between read and write
Jitter is what normally hides the window. The single-packet attack removes jitter.

Where to look

  • Redeem coupon / gift card / referral credit
  • Withdraw, transfer, refund
  • Follow, like, vote, rate (one-per-user limits)
  • Accept invite, join team (seat limits)
  • Password reset token consumption
  • 2FA / OTP validation (bypasses the attempt counter)
  • Email change confirmation
  • Anti-bruteforce counters
  • Free-trial provisioning

Single-packet attack

Puts 20-30 requests in one TCP packet so network jitter can’t spread them. HTTP/2 only.

Burp Repeater > select tabs > Send group in parallel (single-packet attack)

Turbo Intruder:

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=1,
                           engine=Engine.BURP2)
    for i in range(20):
        engine.queue(target.req, gate='race1')
    engine.openGate('race1')

Last-byte sync (HTTP/1.1)

No HTTP/2? Open N connections, send everything but the final byte, wait, then release the last bytes together.

Burp Repeater > Send group in parallel  (falls back to last-byte sync on HTTP/1.1)

Warm the connection first

The first request on a connection is slower. Send a junk request before the group so TLS and the server-side pool are already hot.

Signals

  • The same nonce / token / code accepted twice
  • Balance goes negative
  • Two rows with the same “unique” value
  • Limit of 1 becomes 2 — that is already the bug, do not chase 20
  • A 500 that only appears under parallel load

Multi-endpoint races

Two different endpoints on the same state. Classic: add item to cart while the payment is being captured.

POST /cart/add        }  sent together
POST /checkout/pay    }

Single-endpoint, different values

Two email-change confirmations submitted at once can leave the account bound to one address and the confirmation to another.

Partial construction

Send a request the moment an object exists but before it is initialised — a user row created before its permissions row is written.

Articles

URL Description
https://portswigger.net/research/smashing-the-state-machine The single-packet attack
https://portswigger.net/web-security/race-conditions Labs
https://github.com/PortSwigger/turbo-intruder Turbo Intruder
↑↓ navigate↵ openesc close