WEBVERSE

Loading...

BowBuy
easyPro

BowBuy

A members-only archery shop hands every new face a welcome credit — generous, until you notice nothing on the racks costs less than it. See what you can carry out the door anyway.

race-conditiontoctoubusiness-logice-commercecheckoutflask
pythonflasksqlitenginxgunicorn

The Scenario

BowBuy runs lean: a Blue Ridge pro shop that went online, a small crew, and a launch promo that drops a little store credit into every new member's account to get them started.

The catalog, though, is all premium glass and carbon — every rig priced just out of reach of that welcome handout. The crew are sure no new member walks out with a bow on day one. Prove them wrong, and the thank-you gift is yours.

Lab Intel

Synopsis

A premium archery store whose new-member '$300 welcome credit' is applied at checkout through a check-then-act race. The redeem endpoint reads a one-time flag, pauses to 'authorize', then adds the credit and sets the flag — so concurrent redeems all pass the check and each adds $300. Stack the credit past an item's price, place the order, and the confirmation page renders the gift token (the flag).

Architecture

An easy single-app lab: an nginx gateway in front of one Flask storefront run under gunicorn with a single worker and a thread pool (--workers 1 --threads 8) over SQLite (WAL). New accounts start with balance 0 and welcome_redeemed 0; every product is priced above the $300 welcome credit (cheapest item $329), so no single legitimate purchase clears. POST /checkout/redeem is the flaw: it CHECKS the one-time flag with a plain read (no lock held), pauses ~70 ms to 'authorize the credit' (releasing the GIL), then USES it — an atomic SQL increment balance = balance + 300 plus welcome_redeemed = 1. Because the check and the set are not atomic, ~10-15 concurrent redeems all pass the check inside the window and each adds $300, stacking the balance to $600/$900/$2400…. With balance >= price, POST /checkout/place succeeds (its debit is a single atomic UPDATE ... WHERE balance >= total, so it cannot be double-spent or driven negative) and creates a paid order; GET /order/<id>/confirmation — gated to the session user's own paid order — renders the flag as a 'gift with purchase'. Everything else is hardened so the race is the only path: all prices and the order total are computed server-side, the credit amount is server-fixed, quantity is clamped to a positive integer, and the confirmation page is IDOR-safe (id + owner + status checked in one query). Solve with Burp Turbo Intruder (or a ~15-thread script) firing many /checkout/redeem requests at once, then place the order.

Who It's For

Players meeting race conditions for the first time. You should be comfortable sending many requests concurrently (Burp Turbo Intruder, a threaded Python script) and reasoning about what happens when a check and its effect are not atomic.

Skills You'll Practice

  • Identifying a check-then-act (TOCTOU) race in business logic
  • Distinguishing logic that is safe sequentially but unsafe under concurrency
  • Driving a race with a concurrent request tool and confirming the stacked state
  • Recognising that a one-time guard must be atomic with the effect it protects

What You'll Gain

  • A working mental model of TOCTOU / check-then-act races
  • Why 'it's a one-time action' is not a guarantee without atomicity
  • A reusable approach for racing one-time credits, coupons, and redemptions

Ready to hack BowBuy?

Upgrade to Pro to unlock this lab and the full library.