npx skills add ...
npx skills add yaklang/hack-skills --skill race-condition
Race condition and TOCTOU testing for web apps. Use when testing one-time operations, concurrent HTTP abuse, rate-limit bypass, Turbo Intruder gates, HTTP/2 single-packet attacks, and CWE-362-style synchronization gaps.
npx skills add yaklang/hack-skills --skill race-condition
AI LOAD INSTRUCTION: Treat race conditions as authorization/state integrity issues: non-atomic read-then-write lets multiple requests observe stale state. Prioritize one-time or balance-like operations. Combine parallel transport (HTTP/1.1 last-byte sync, HTTP/2 single-packet, Turbo Intruder gates) with application evidence (duplicate success responses, inconsistent balances, duplicate ledger rows). Authorized testing only. Routing note: for business workflows, coupons, inventory, or one-time rewards, start with this skill and cross-load
business-logic-vulnerabilities.
Target endpoints where check and update are unlikely to be a single atomic database operation:
| Priority | Operation class | Example paths / parameters |
|---|---|---|
| 1 | One-time redeem / coupon / bonus | redeem, apply_coupon, claim_reward, voucher |
| 2 | Balance / quota / stock deduction | transfer, purchase, reserve, inventory |
| 3 | Invite / referral / signup bonus | invite_accept, referral_claim |
| 4 | Password / email / MFA verification | verify_token, confirm_email, reset_password |
| 5 | Idempotent-looking APIs without strong keys | POST that should succeed only once per user |
First moves (conceptual):
TOCTOU means the decision (check) and the mutation (use) are not one indivisible step.
Typical vulnerable pseudo-flow:
Two concurrent requests can both pass the if before either UPDATE commits.
| Layer | What goes wrong |
|---|---|
| Application | In-memory flag, cache, or session says "not used yet" while DB already updated — or the reverse. |
| ORM / service | Two instances, no distributed lock; each thinks it owns the decision. |
| DB | Missing SELECT … FOR UPDATE, wrong isolation level, or logic split across multiple statements without transaction. |
| API gateway | Per-IP rate limit is check-then-increment — parallel burst passes duplicate checks. |
Hint: UNIQUE constraints and idempotency keys often eliminate entire bug classes — test whether the app enforces them on the hot path.
Send the same authenticated request many times in parallel:
Success signal: HTTP 200/201 more than once, duplicate ledger entries, or balance higher than policy allows.
If limits are implemented as counters checked per request without atomic increment:
Fire N parallel attempts in one wave; compare with N sequential attempts.
Success signal: more failures accepted than documented cap, or lockout never triggers when burst completes inside one window.
Workflow: create → pay → confirm. If confirm does not cryptographically bind to pay completion:
Success signal: item marked paid/shipped without matching payment, or state skips backward.
Idea: Hold all requests blocked until every socket has sent the full request except the last byte of the body; then release the final byte together so the server receives them in a tight cluster.
Why: Reduces network jitter between copies compared to naive sequential paste in Repeater.
Tooling: Custom scripts, some Burp extensions, or Turbo Intruder gate pattern (see §5) as the practical stand-in for synchronized release.
Idea: Multiplex several complete HTTP/2 streams and coalesce their frames so the first bytes of all requests exit the NIC in one TCP segment (or minimally separated). Receiver-side scheduling then processes them with sub-millisecond spacing.
Burp Repeater (modern workflows):
Why it often beats HTTP/1.1 last-byte tricks: tighter alignment on the wire; less dependence on per-connection serialization.
Repository: PortSwigger/turbo-intruder (Burp Suite extension).
Settings: concurrentConnections=30, requestsPerConnection=30, use a gate so all threads fire together.
Core pattern (repeat N times, then release):
Header requirement (unique per queued copy for log correlation; Turbo Intruder payload placeholder):
Turbo Intruder replaces %s per request when paired with a wordlist (or other payload source) — keep this header on the base request in Repeater before sending to Turbo Intruder. Case-insensitive for HTTP; use a consistent name for log grep.
Pattern: One POST to target-1 (state change) plus many GETs to target-2 (read side) released together to widen the TOCTOU window observation.
Adjust hosts/paths by duplicating RequestEngine instances if endpoints differ (Turbo Intruder supports multiple engines — consult upstream docs for your Burp version).
CVE-2022-4037 (GitLab CE/EE): race condition leading to verified email address forgery and risk when the product acts as an OAuth identity provider — third-party account linkage/impact scenarios. CWE-362. Demonstrated in public research with HTTP/2 single-packet style timing to win narrow windows.
Takeaway for testers: email verification, OAuth linking, and "confirm ownership" flows are high-value race targets — not only coupons and balances.
References (official / neutral):
| Tool | Role |
|---|---|
| PortSwigger/turbo-intruder | High-concurrency replay, gates, scripting in Burp. |
| JavanXD/Raceocat | Race-focused HTTP client patterns (verify compatibility with your stack). |
| nxenon/h2spacex | HTTP/2 low-level / single-packet style experimentation (use responsibly, authorized targets only). |
| Burp Suite — Repeater | Send group (parallel) / single-packet attack for multi-request synchronization. |
How to confirm (evidence checklist):
x-request (or similar) markers or unique body fields in logs (authorized environments).Routing summary: if the scenario is more about business rules, pricing, or workflow bypass, load skills/business-logic-vulnerabilities/SKILL.md; this file focuses on concurrency and transport-layer synchronization.
TCP's Nagle algorithm (RFC 896) buffers small writes and coalesces them into fewer, larger segments. When an HTTP/2 client writes multiple HEADERS+DATA frames in rapid succession without flushing between them, the kernel merges them into a single TCP segment (up to MSS, typically ~1460 bytes on Ethernet).
TCP_NODELAY disabled (default) → Nagle active → coalescing happens naturallyTCP_NODELAY is set, the client must use writev() / gather-write syscall to batch framesrecv() syscall returns the entire segmentFirst-to-last request dispatch gap: < 100 μs on modern servers — orders of magnitude tighter than HTTP/1.1 last-byte sync (~1–5 ms network jitter).
| Factor | HTTP/2 Single-Packet | HTTP/1.1 Last-Byte |
|---|---|---|
| Connections needed | 1 | N (one per request) |
| Wire synchronization | Same TCP segment | N segments released "simultaneously" |
| Network jitter impact | Zero (same packet) | Each connection has independent RTT |
| Server dispatch gap | < 100 μs | 1–5 ms typical |
| Practical limit | ~20–30 requests per MTU | Limited by connection setup |
| Isolation Level | Phenomenon Exploited | Attack Window | Typical Vulnerable Pattern |
|---|---|---|---|
| READ UNCOMMITTED | Dirty reads | Thread B reads Thread A's uncommitted write | SELECT balance sees in-flight deduction, proceeds with stale logic |
| READ COMMITTED | Non-repeatable reads (TOCTOU) | Both threads read committed balance, both pass check, both deduct | SELECT → app check → UPDATE without FOR UPDATE |
| REPEATABLE READ | Phantom reads | Snapshot isolation hides concurrent inserts; both threads see "0 claims" and insert | INSERT IF NOT EXISTS pattern without UNIQUE constraint |
| SERIALIZABLE | Advisory lock bypass | Application uses pg_advisory_lock() / GET_LOCK() with wrong scope or derivable key | Lock key from user input; session-vs-transaction scope mismatch |
Fix verification: SELECT ... FOR UPDATE should block Thread B's SELECT until Thread A commits.
Fix: UNIQUE(user_id, coupon_id) constraint causes one INSERT to fail with duplicate key error regardless of isolation level.
Variations: same coupon across different cart items; apply-coupon + checkout in parallel (coupon consumed only at checkout).
Higher-value variant: withdrawal to external system (crypto, bank wire) where reversal is difficult.
Compound attack: add-to-cart and checkout are separate steps, each checking inventory independently.
Instead of N copies of the same request, send requests to different endpoints in one HTTP/2 single-packet burst. This widens the TOCTOU window by hitting both the check and use paths simultaneously.
Balance inconsistency between stream 1 and stream 7 confirms the race window was hit.
If coupon application and checkout check prices independently, the discount may apply after checkout has locked the price.
Upgrade may succeed during the brief window where verification is processing but not yet committed.
Burp Repeater: add requests targeting different paths to the same group → "Send group (single packet)".
../business-logic-vulnerabilities/SKILL.md).