Athena Wiki
Web ExploitationServer-Side Attacks

Race Conditions & TOCTOU

webintermediate

Exploit timing windows in CTF web apps - parallel requests, limit bypasses, upload races, and time-of-check/time-of-use flaws.

webrace-conditiontoctouconcurrencytimingburpbusiness-logic

Race Conditions & TOCTOU

Race conditions happen when the order of operations on shared state matters, but the app does not enforce a single safe ordering. In CTF web challenges, you usually win by sending many requests at once so two actions interleave in a way the author did not expect - for example redeeming a one-time code twice, spending the same balance twice, or slipping a payload between “check” and “use”.

TOCTOU (time-of-check to time-of-use) is the classic pattern: the server checks something (quota, file type, ownership), then acts - and you change the world between those steps (CWE-367).

Mermaid diagram

Where Race Bugs Hide in CTF

FeatureWhat breaksWhat you try
Coupon / promo code“One use” enforced only in app logicParallel POST with same code
Wallet / creditsRead balance → subtract → writeDouble-spend burst
Inventory“Stock > 0” then decrementTwo purchases at same stock
Email / password changeToken valid once, two tabsUse token twice in parallel
Rate limitCounter in Redis without atomic decrBurst past limit
File uploadValidate extension → move to web rootRace swap file after check

Black-Box Testing Workflow

  1. Map stateful endpoints: anything that checks a condition and then updates a row, file, or session.
  2. Send a burst of identical or complementary requests (20–500 is common in CTF).
  3. Look for extra successes: HTTP 200 on both “redeem”, double balance change, or inconsistent JSON.

Sending Parallel Requests

Burp Suite

  • Send the request to Repeater, then use Send group (or Turbo Intruder) to fire many copies in parallel.
  • Turn inter-request delay off; use maximum concurrency the server allows.

Python (threading)

import threading
import requests

URL = "http://target/redeem"
COOKIE = {"session": "..."}
BODY = {"code": "PROMO123"}

def fire():
    r = requests.post(URL, cookies=COOKIE, data=BODY, timeout=5)
    print(r.status_code, r.text[:200])

threads = [threading.Thread(target=fire) for _ in range(50)]
for t in threads:
    t.start()
for t in threads:
    t.join()

Python (asyncio) - good for high concurrency:

import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [
            session.post("http://target/redeem", data={"code": "PROMO123"})
            for _ in range(100)
        ]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        for r in results:
            print(getattr(r, "status", r))

asyncio.run(main())

TOCTOU: Upload Then Move

Sometimes the server:

  1. Saves upload to /tmp/abc123.jpg
  2. Checks magic bytes / extension
  3. Moves to /var/www/uploads/abc123.jpg

If you can replace /tmp/abc123.jpg between (2) and (3) with a symlink or different file (depends on FS permissions), you can win - in CTF this is rare but shows the mental model.

More common: two uploads with the same predictable name or race on session file ID.

HTTP/2 and Single-Packet Tricks

Some servers multiplex requests on one connection. Tools like Turbo Intruder or race-focused scripts try to land requests in the same window. If a challenge mentions “single-packet” or “HTTP/2”, read the latest writeups on request smuggling vs race - they are different bugs, but the tooling (Burp HTTP/2, custom framing) overlaps.

Defensive Patterns (Recognize Weak Fixes)

Wrong fixWhy it still races
Check-then-update in two queriesTwo threads both pass the check
Application-level mutex onlyMultiple workers / multiple servers
SELECT then UPDATE without transactionClassic lost update

Stronger (what writeups cite as the fix):

  • UPDATE wallets SET balance = balance - 10 WHERE balance >= 10 AND id = ?
  • Serializable isolation or SELECT ... FOR UPDATE
  • Idempotency keys on payment endpoints
  • Single-threaded queue per user resource

Last updated on

On this page