Cyberlium

Web › Module 6 › Lesson 3

BeginnerModule 6Lesson 3/5

Race Conditions and Limit Overrun

Two requests, one limit — design must serialize the truth. Local counter only.

15 min+40 XP3 quiz
Module progress3 of 5

Visual · race_limit_overrun

Two arrows hit one counter. Limit says 1; both see 0; both write 1. DEMO http://192.168.0.1/ or SAFE Never a bank-steal race.

Opening

Races and limit overruns are timing against a rule you forgot to lock.

OWASP Top 10:2025 A06 Insecure Design includes concurrency failures: a one-time coupon checked then marked used in two steps without a transaction; a seat counter read-modify-write without locking; a “max five” limit that two parallel requests both pass. The attacker goal is to overrun a business limit. Cyberlium teaches the mechanism with a local HTTP counter YOU own on — Identify the banner first; router login is OUT OF SCOPE. Not a recipe to drain a bank, not a gift-card double-spend against a live brand. Attack literacy → own-lab → fix/report. Fix means atomic check-and-set, unique constraints, idempotency keys, and queues — named on your stack. Next is the design review lab. Today: race literacy without exploit kits.

1. Check-then-act: the gap where two truths fit

Classic pattern: read limit, decide OK, write new state. Between read and write, another request does the same. Both saw “available.” Both succeeded. The design assumed single-threaded humans. The fix is not “hope.” The fix is one atomic operation or a uniqueness constraint the second write cannot violate. You will demonstrate that gap on a toy HTTP counter under cyberlium-lab — never on a payment network.

Limit overrun is the business name for the same gap when the limit is quantity, budget, invites, or “once.” Rate limiting at the edge helps bots; it does not replace transactional integrity for money-like counters.

Command guide

A06 Design — Check-then-act

═══ INSTALL ═══

Linux (Debian/Ubuntu):

Command — copy this

sudo apt install curl

macOS: Built-in

Windows: Built-in (PowerShell: Invoke-WebRequest)

═══ COMMANDS ═══

Command — copy this

SAFE="http://127.0.0.1:8773"

Command — copy this

curl -sS -m 3 "$SAFE/counter" | head -n 12
curl -sS -m 3 -X POST "$SAFE/counter/inc" | head -n 12

2. Design controls: transactions, uniqueness, idempotency

Name the controls you would require in a design review: database transaction with row lock or serializable isolation for the counter; UNIQUE(user_id, coupon_id) so the second insert fails; Idempotency-Key on payment-like POSTs so retries do not double-apply; queue a single worker for contested resources. You do not need a vendor cookbook — you need the sentence “two concurrent applies cannot both succeed.”

Idempotency is design: the client may retry; the server must treat duplicate keys as one effect. Document where keys are stored and how long. Empty “we will add locks later” fails A06.

Command guide

A06 Design — Design controls

═══ INSTALL ═══

Linux (Debian/Ubuntu):

Command — copy this

sudo apt install curl

macOS: Built-in

Windows: Built-in (PowerShell: Invoke-WebRequest)

═══ COMMANDS ═══

Command — copy this

SAFE="http://127.0.0.1:8773"

Command — copy this

curl -sS -m 3 -I "$SAFE/" | head -n 10

3. What this course refuses: bank-steal and live double-spend PoCs

Public writeups sometimes show racing checkout on real brands. That is out of scope here. Your lab is a Python HTTP counter in $HOME/cyberlium-lab that intentionally has a check-then-act bug, then a fixed version with a lock. No card numbers. No foreign hosts. No hydra on the router. No nmap of 192.168.0.0/24. If it feels too small without real money, that feeling is how people cross into unauthorized testing.

Report shape on YOUR app: asset http://192.168.0.1/ or SAFE, steps on loopback, expected single success, actual double success, invariant, proposed atomic control. Stop.

4. Observability: log the denied second apply

When uniqueness or locks deny the second writer, log actor, resource, and outcome without secrets. A09 will deepen logging; A06 already needs the design to emit that signal so limit abuse is visible. Silent success on both writers is the insecure design.

Carry forward: races are design until proven otherwise. Implementation without locks is how the design fails in production.

5. Wrong vs right: live payment races vs local counter literacy

Same word “race,” opposite job. Right never needs a bank or a router login.

  • Wrong

    Publish or run bank-steal / gift-card double-spend PoCs. Race a stranger checkout. Hydra 192.168.0.1. nmap the LAN. Skip atomic controls. Store session tokens in the note.

  • Right

    Identify DEMO; if router, SAFE 127.0.0.1:8773. Explain check-then-act; run broken vs fixed local HTTP counter YOU wrote; name transaction/unique/idempotency; lock race-limit-notes.txt. Next: Lab — Design Review Note.

6. Hands-on: HTTP counter broken vs fixed (8773)

Start a06_counter_toy.py (binds 127.0.0.1:8773). GET /broken/apply (check-then-act, may overrun). GET /fixed/apply (lock). Two curls close together on broken; record. No bank. No hydra.

Command guide

race_limit_local_counter.sh — DEMO identify + HTTP counter 8773

═══ INSTALL ═══

Linux (Debian/Ubuntu):

Command — copy this

sudo apt install curl
sudo apt install nmap
sudo apt install python3

macOS:

Command — copy this

brew install nmap
brew install python3

Windows: Built-in (PowerShell: Invoke-WebRequest)

Command — copy this

choco install nmap  # or download https://nmap.org/download.html

Download https://python.org/downloads/

═══ COMMANDS ═══

Command — copy this

cd "$HOME/cyberlium-lab"

Command — copy this

cat > a06_counter_toy.py << 'PY'
"""Teaching check-then-act vs lock. Bind loopback only."""
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
import threading
import time
from urllib.parse import urlparse

HOST = "127.0.0.1"  # bind only — never 0.0.0.0, never argv
PORT = 8773
if HOST != "127.0.0.1":
raise SystemExit("refusing non-loopback bind")

LIMIT = 1
broken_n = 0
fixed_n = 0
lock = threading.Lock()

class Handler(BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
    print("[demo]", fmt % args)

def _send(self, code: int, body: str):
    data = body.encode("utf-8")
    self.send_response(code)
    self.send_header("Content-Type", "text/plain; charset=utf-8")
    self.send_header("Content-Length", str(len(data)))
    self.end_headers()
    self.wfile.write(data)

def do_GET(self):
    global broken_n, fixed_n
    p = urlparse(self.path).path
    if p == "/broken/apply":
        n = broken_n
        time.sleep(0.08)
        if n < LIMIT:
            broken_n = n + 1
            return self._send(200, f"broken applied n={broken_n}")
        return self._send(409, f"broken denied n={broken_n}")
    if p == "/fixed/apply":
        with lock:
            if fixed_n < LIMIT:
                fixed_n += 1
                return self._send(200, f"fixed applied n={fixed_n}")
            return self._send(409, f"fixed denied n={fixed_n}")
    if p == "/reset":
        broken_n = 0
        fixed_n = 0
        return self._send(200, "reset")
    return self._send(
        200,
        "Cyberlium A06 counter toy. DEMO writeup: http://192.168.0.1/ "
        "GET /broken/apply  GET /fixed/apply  GET /reset  LIMIT=1",
    )

print("bind", HOST, PORT)
print("demo_writeup_url http://192.168.0.1/")
print("if 192.168.0.1 is a router, curl", f"http://{HOST}:{PORT}/")
ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
PY

Command — copy this

cat > a06_l03_practical_commands.sh << 'SH'

wait

wait

SH

Command — copy this

{

Mission: race-limit-notes.txt + local counter (mode 600)

1) 0.0.1:8773 after starting a06_counter_toy.py.2) Define check-then-act; run broken vs fixed HTTP counter curls; name lock/unique/transaction/idempotency. 3) chmod 600 $HOME/cyberlium-lab/race-limit-notes.txt. No bank PoCs. No hydra. No nmap.

Stuck? Ask Cyberlium AI Mentor

If the lesson feels “fake” without a payment race, ask for a hint — not a merchant target. Try: "Hint only: why check-then-act overruns a limit; what UNIQUE or a lock changes; why 192.168.0.1 router login is OUT OF SCOPE; where my local counter lives?" You still use cyberlium-lab only.

Race literacy without bank-steal kits: two requests, one limit, design must serialize truth — proven with DEMO/SAFE curls on 8773. A06 Insecure Design. Original Cyberlium — not official OWASP certification. Next — Lab — Design Review Note — pull threat model, logic, and race lines into one review on an app you own.

Knowledge Check

1

APPLY: Two parallel requests both pass “coupon unused” then both mark used. What failed, and what is in-scope for Cyberlium?

Multiple choice

Knowledge Check

2

APPLY: True or False: Edge rate limits alone make one-time coupon races impossible.

True or False

Knowledge Check

3

APPLY: curl of http://192.168.0.1/ is a Netgear router login. Race lab next step?

Multiple choice

← Previous

Answer all 3 knowledge checks to continue. (0/3 answered)