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.

BowBuy — WebVerse Pro Lab