Todo
Challenge
Content preserved from the original writeup source. Minimal normalization was applied to fit platform format.
Solution
Original Writeup Content (Preserved)
squirrel-todo (web) - Full Writeup
Challenge Info
- Category: web
- Name: todo
- Author: nisala
- Solves/Points at solve time: 60 solves / 213 points
- Target: https://todo.squ1rrel.dev
Short Description
The app looked like a normal todo frontend, but the challenge hint said the frontend subagent crashed before wiring everything to backend. That strongly suggested hidden backend functionality still deployed and reachable without UI links.
This challenge is solved by finding and calling an unlinked server function endpoint directly.
Final Flag
squ1rrel{tree_shaking?\_nah_we_dont_do_that_here}
squirrel-todo (web) - Comprehensive Writeup (Student Version)
Challenge Info
- Category: web
- Name: todo
- Author: nisala
- Solves/Points at solve time: 60 solves / 213 points
- Target: https://todo.squ1rrel.dev
Final Flag
squ1rrel{tree_shaking?\_nah_we_dont_do_that_here}
What the Flag Means (Student Explanation)
The flag text is a direct hint about the vulnerability class:
tree_shaking?- Tree shaking is the build step that removes unused code from production bundles.
nah_we_dont_do_that_here- This means unused or internal functionality was not removed properly.
How that maps to this challenge:
- A backend server function existed but was not linked in the visible UI.
- Its function ID still appeared in frontend JavaScript assets.
- That made the hidden backend functionality discoverable and callable directly.
- Sending valid input to that hidden function returned the flag.
So the flag is not random flavor text. It summarizes the exact exploitation path: exposed "dead"/unlinked functionality due to incomplete cleanup (no effective tree-shaking/removal of sensitive callable paths).
Why This Writeup Is Different
This version is written for students and newer CTF players. It includes:
- the exact solve path
- why each step matters
- what clues to notice
- common mistakes and troubleshooting
- a reproducible exploit script
Learning Objectives
By the end of this challenge, you should be able to:
- Extract hidden backend endpoints from frontend JavaScript bundles.
- Distinguish between visible UI functionality and deployed backend functionality.
- Use validation errors to infer API schema quickly.
- Build a minimal exploit once request format is understood.
Key Idea in One Sentence
The frontend only exposed normal todo features, but an unlinked backend server function was still deployed and callable directly.
Terminology (Quick Glossary)
- Bundle: The minified JavaScript file shipped to the browser.
- Server function: A backend callable endpoint that may be represented as a function in frontend code.
- RPC-style endpoint: An endpoint that behaves like calling a function with arguments, instead of a classic REST route.
- Tree shaking: Build optimization that removes unused code. If not done correctly, internal references may leak.
High-Level Strategy
- Recon the app behavior and loaded assets.
- Inspect frontend bundles for hidden endpoint references.
- Enumerate method expectations and data contracts.
- Call the hidden endpoint with valid data.
- Extract flag.
Step-by-Step Walkthrough
Step 1: Recon and Observation
When opening the website, the visible UI looked like a normal todo list:
- create todo
- mark complete
- delete todo
At this stage, there is no visible "admin" or "flag" functionality.
Important clue from challenge text:
- "frontend subagent crashed before linking everything"
Interpretation:
- Some backend feature likely exists but is not connected in the UI.
Step 2: Inspect Frontend Bundles
The page loads JavaScript bundles from /assets/.... After downloading and searching them, the routes bundle exposed server function IDs in this pattern:
/_serverFn/<64-hex-hash>
Discovered hashes:
- 0ea84404b23101964c7526b38c25485f6431d1909986dd79241d794d0b6cf9a8
- 8c2faf88db29bb285ee4c696eb40a1cf64bb1adc38394328d54a0e3f044c6682
- b7ca1f24f3b2c3af70c4e3c73a081d0c6ba50ea5fe9d8a969eba1c6524845c14
- 0e1890c81aed74728854c3163c6d77285f995512a99fddd1c37b3505daf9e3be
- 3633763ff4da33d65cb24e276f877dcaa1972bfb59429377abc55a408a83167a
Why this matters:
- Hash-based endpoint IDs are often not guessable, but if they are embedded in client code, you can still call them directly.
Step 3: Classify Each Endpoint
Testing the discovered hashes revealed:
- Four hashes handled regular todo operations and expected
GET. - One hash expected
POST:3633763ff4da33d65cb24e276f877dcaa1972bfb59429377abc55a408a83167a
This POST endpoint was suspicious because it was not part of the visible todo workflow.
Step 4: Infer Input Schema from Error Messages
Calling the hidden POST function with invalid JSON produced structured validation errors:
field1must bestringfield2must benumber
Why this is a big win:
- Validation errors give you the contract for free.
- You do not need source code if the server tells you input expectations.
Step 5: Send Valid Input
Sending any valid pair like:
field1: "a"field2: 1
returned the flag in the result field.
Exploit (Python, Reproducible)
#!/usr/bin/env python3
import requests
BASE = "https://todo.squ1rrel.dev"
HIDDEN_HASH = "3633763ff4da33d65cb24e276f877dcaa1972bfb59429377abc55a408a83167a"
URL = f"{BASE}/_serverFn/{HIDDEN_HASH}"
# Valid schema inferred from server errors:
# field1 -> string
# field2 -> number
payload = {
"field1": "student",
"field2": 1,
}
headers = {
"x-tsr-serverFn": "true",
"accept": "application/json",
"content-type": "application/json",
}
with requests.Session() as sess:
resp = sess.post(URL, json=payload, headers=headers, timeout=15)
resp.raise_for_status()
data = resp.json()
print("HTTP:", resp.status_code)
print("Raw JSON:", data)
print("Flag:", data.get("result"))
Minimal Request Explanation (For Students)
Why these headers are used:
x-tsr-serverFn: true- Signals this is a framework server-function call.
content-type: application/json- Payload is JSON.
accept: application/json- Ask for JSON response.
Why requests.Session() is used:
- Keeps cookies automatically (some apps rely on session cookies between calls).
Requirements
requirements.txt
requests>=2.31.0
Environment Setup
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python exploit.py
Expected Output
The response includes:
result: "squ1rrel{tree_shaking?_nah_we_dont_do_that_here}"
Common Mistakes and Fixes
Mistake 1: Wrong HTTP method
Symptom:
expected POST method. Got GET
Fix:
- Use
POSTfor the hidden hash endpoint.
Mistake 2: Invalid payload fields
Symptom:
- Validation errors for
field1andfield2.
Fix:
- Send
field1as string andfield2as number.
Mistake 3: Missing framework header
Symptom:
- Non-standard or unexpected server response.
Fix:
- Include
x-tsr-serverFn: true.
Why the Vulnerability Exists
This is an exposed internal function issue:
- Backend function exists in production.
- Function ID is leaked through frontend bundle.
- Function is callable directly.
- Function returns sensitive data after only basic shape validation.
Defensive Recommendations (Real-World)
- Remove unused server functions during build/deploy.
- Never rely on "not linked in UI" as security control.
- Require authorization checks in every sensitive backend function.
- Avoid returning detailed validation internals in production when possible.
- Add automated scans for leaked endpoint identifiers in public bundles.
Student Takeaways
- Frontend visibility is not backend security.
- Validation errors are reconnaissance tools.
- Bundle analysis is often enough to discover hidden attack surface.
- Challenge hints often describe the exact bug class indirectly.
Optional: Save as Separate Files
exploit.py
Copy the Python exploit block above into exploit.py.
requirements.txt
Create with:
requests>=2.31.0
Final Summary
This challenge was not about complex crypto, SQLi, or RCE. It was about identifying a deployment mistake: an unlinked but exposed server function. Once the hidden endpoint and schema were discovered, the flag retrieval was straightforward.
- Validation errors are useful for discovering accepted schema quickly.