But here's the problem:
Most real-world API vulnerabilities don't come from complex exploits — they come from simple design mistakes.
To understand this deeply, I built a deliberately vulnerable API, exploited it, and then hardened it step by step.
This hands-on approach helped me move beyond theory and actually understand how API security breaks in practice.

🧱 Project Overview
In this lab, I built a simple Flask-based API with three endpoints:
/fetch → Fetch external URLs (SSRF)
/user/<id> → Fetch user data (IDOR)
/admin → Admin access controlEach endpoint was intentionally designed to demonstrate a real-world vulnerability.
⚠️ Phase 1 — Building a Vulnerable API
Let's break down what was wrong.
🔴 1. SSRF (Server-Side Request Forgery)
Vulnerable Code
@app.route("/fetch")
def fetch():
url = request.args.get("url")
response = requests.get(url)
return response.text❌ What's the problem?
The server blindly trusts user input and makes a request to any URL.
💥 Exploitation
http://127.0.0.1:5000/fetch?url=http://127.0.0.1:5000/user/1👉 The server makes a request to itself (internal service)
🧠 Why this is dangerous
In real-world cloud environments, this can expose:
- IAM credentials
- Metadata services (
169.254.169.254) - Internal APIs
- Private network services
SSRF turns your server into an attack proxy.
🔴 2. IDOR (Insecure Direct Object Reference)
Vulnerable Code
@app.route("/user/<id>")
def get_user(id):
return users.get(id, "User not found")❌ What's the problem?
There is no access control.
💥 Exploitation
/user/1 → Alice
/user/2 → Bob👉 Any user can access any other user's data.
🧠 Why this is dangerous
This leads to:
- Data leakage
- Privacy violations
- Account takeover scenarios
🔴 3. Broken Admin Authorization
Vulnerable Code
@app.route("/admin")
def admin():
role = request.headers.get("Role")
if role == "admin":
return "Welcome admin"❌ What's the problem?
The server trusts a client-controlled header.
💥 Exploitation
curl -H "Role: admin" http://127.0.0.1:5000/admin👉 Instant admin access.
🧠 Lesson
Never trust anything coming from the client.
🛠️ Phase 2 — Hardening the API
Now comes the most important part: fixing the system.
🛡️ Fix 1 — SSRF Protection
Approach
- Parse the URL
- Extract hostname
- Resolve IP
- Block internal/private IP ranges
Hardened Logic (Concept)
User input URL
↓
Parse hostname
↓
Resolve IP
↓
If private → Block
Else → Allow✅ Result
/fetch?url=http://localhost:5000/user/1👉 ❌ Blocked
🧠 Key Insight
SSRF prevention is not just input validation — it requires network awareness.
🛡️ Fix 2 — Proper Authorization (Admin)
Approach
Replace client-controlled role with server-side validation:
token = request.headers.get("Authorization")
if token == "secret-admin-token":
return "Welcome admin"✅ Result
- No token → Access denied
- Valid token → Access granted
🧠 Key Insight
Authorization must always be enforced on the server side.
🛡️ Fix 3 — IDOR Protection
Approach
Ensure users can only access their own data.
if token != id:
return "Unauthorized"✅ Result
- Own data → Allowed
- Other users' data → Blocked
🧠 Key Insight
Every object access must be validated against user identity.
🔍 What This Project Taught Me
This project reinforced a few critical ideas:
1. Security is about assumptions
Every vulnerability came from a wrong assumption:
- "User won't abuse URL input"
- "User will only access their data"
- "Headers can be trusted"
2. APIs fail in simple ways
No complex exploits needed:
- Just changing a URL
- Modifying a header
- Calling internal endpoints
3. Fixing is harder than breaking
Writing vulnerable code is easy. Designing secure systems requires:
- Input validation
- Access control
- Threat modeling
🚀 Final Thoughts
This lab was not about building a perfect system.
It was about understanding:
How APIs break
↓
How attackers think
↓
How to fix systems correctlyBy combining:
- SSRF
- IDOR
- Broken authorization
I now have a much clearer understanding of real-world API security risks.
🔗 Project Links
- GitHub: https://github.com/AshSecures/03-api-security-lab
- Diagrams: Included in repository