Developers build features. Attackers break assumptions. The difference is not skill — it is perspective. This lesson teaches you to look at your own code the way an attacker would, so you can find weaknesses before they do.
The attacker's mindset
Attackers do not read your code top to bottom. They ask questions:
- Where does data enter the system? Every input is a potential attack surface.
- What assumptions does the code make? Every assumption is a potential bypass.
- Where are the trust boundaries? Every boundary is a potential escalation point.
- What happens when things go wrong? Error handling often reveals or enables vulnerabilities.
- What was forgotten? The endpoint nobody remembers, the admin tool with no auth.
Thinking in attack graphs
A defender thinks: "The user logs in, views their dashboard, updates their profile."
An attacker thinks: "What if I skip the login? What if I view someone else's dashboard? What if I update someone else's profile? What if I update fields that should not be updatable?"
Every feature has a happy path and dozens of unhappy paths. Attackers explore the unhappy paths.
Common attacker patterns
Reconnaissance
Before attacking, gather information:
| Technique | What it reveals |
|---|---|
| View page source, JavaScript files | API endpoints, internal paths, framework version |
Check /robots.txt, /sitemap.xml | Hidden pages, admin panels |
| Read error messages | Stack traces, database type, internal IPs |
| Check HTTP headers | Server software, framework, security headers present/missing |
| Browse the API docs | All endpoints including ones the UI does not use |
| Search GitHub for the company name | Leaked credentials, internal documentation, old code |
| DNS enumeration | Subdomains, staging environments, internal tools |
What this means for you: Assume attackers know your tech stack, your API surface, and your endpoint structure. Security through obscurity does not work.
Parameter manipulation
Attackers modify every parameter they can see:
# Normal request
GET /api/orders/12345
Authorization: Bearer <user_token>
# Attacker tries another user's order
GET /api/orders/12346
Authorization: Bearer <user_token>
# Attacker tries admin endpoint
GET /api/admin/orders
Authorization: Bearer <user_token>
# Attacker manipulates query parameters
GET /api/orders?userId=admin&status=all
Authorization: Bearer <user_token>
What this means for you: Authorise every request based on the authenticated user's permissions, not just whether a valid token is present. Never trust client-supplied IDs without ownership checks.
Privilege escalation
Attackers look for ways to gain more access than they should have:
| Type | Example |
|---|---|
| Vertical | Regular user gains admin access |
| Horizontal | User A accesses User B's data |
| Context | Free-tier user accesses premium features |
Common escalation vectors:
- Changing a
roleparameter in a request body - Modifying a JWT payload (if the signature is not verified)
- Accessing admin API endpoints directly (they rely on UI hiding instead of server-side checks)
- Exploiting race conditions during permission checks
Chaining vulnerabilities
Individual low-severity issues combine into critical exploits:
Low: Username enumeration on login (different error for "user not found" vs "wrong password")
+
Low: No rate limiting on password reset
+
Medium: Password reset token is predictable (timestamp-based)
=
Critical: Account takeover of any user
Attackers chain findings. A "low" severity issue is only low in isolation.
Attack surface mapping
Identify all entry points
For every application, enumerate:
Network entry points:
- Public web endpoints
- API endpoints (REST, GraphQL, gRPC, WebSocket)
- Admin panels
- Health check / monitoring endpoints
- Webhook receivers
- File upload endpoints
Data entry points:
- Form fields
- URL parameters and path segments
- HTTP headers (Host, Referer, X-Forwarded-For)
- Cookies
- File uploads (name, content, MIME type)
- API request bodies
Indirect entry points:
- Email content processed by the application
- Data imported from third-party integrations
- Messages from queues or event streams
- DNS records (for applications that process them)
- CI/CD pipeline inputs (PR titles, branch names, commit messages)
Prioritise by risk
Not all entry points are equal. Focus on:
- Unauthenticated endpoints — accessible to anyone on the internet
- Endpoints that process untrusted input — file uploads, search, user-generated content
- Endpoints that access sensitive data — PII, financial data, credentials
- Endpoints with elevated privileges — admin functions, user management
- Forgotten or deprecated endpoints — old API versions, debug endpoints
Assumption hunting
Every vulnerability is a broken assumption. Train yourself to spot them:
| Assumption | Reality |
|---|---|
| "Users will only submit the form fields we show them" | Attackers craft arbitrary HTTP requests |
| "The client-side validation prevents bad input" | Client-side validation is trivially bypassed |
| "Only admins can access /admin routes" | Unless the server checks, anyone can try |
| "The user ID comes from the session, so it's trusted" | Unless the endpoint also accepts a userId parameter in the body |
| "Nobody will guess that URL" | Predictable IDs, directory brute-forcing |
| "We don't process XML, so XXE doesn't apply" | A library dependency might parse XML internally |
| "The file upload only accepts images" | MIME type and extension checks are easily faked |
| "Rate limiting prevents brute force" | Distributed attacks from thousands of IPs |
Exercise: Question your current project
For every feature you are building or have built, ask:
- What input does this accept? Can I send unexpected values?
- Who should have access? Is that enforced on the server?
- What happens if I send this request twice? A hundred times? A million times?
- What happens if I modify the ID, the role, the amount, the email?
- What happens if I skip step 2 and go directly to step 3?
- What data does the error message reveal?
The STRIDE model for developers
STRIDE is a threat classification that maps well to attacker thinking:
| Threat | Question to ask |
|---|---|
| Spoofing | Can I pretend to be someone else? |
| Tampering | Can I modify data I should not be able to? |
| Repudiation | Can I do something and deny it happened? |
| Information disclosure | Can I access data I should not see? |
| Denial of service | Can I make the system unavailable? |
| Elevation of privilege | Can I gain access beyond my authorisation? |
For every endpoint, walk through each letter. If the answer to any question is "maybe," you have found something to investigate.
Practical exercises
1. Browser DevTools exploration
Open your application in Chrome DevTools:
- Network tab: Watch every request. Note endpoints, parameters, headers, response data.
- Application tab: Check cookies, localStorage, sessionStorage for tokens and sensitive data.
- Console: Look for JavaScript errors that reveal internal details.
- Sources: Read client-side JavaScript for hardcoded secrets, API keys, internal endpoints.
2. Intercept and modify requests
Use your browser's DevTools or a proxy (Burp Suite, OWASP ZAP) to:
- Capture a normal request
- Change the user ID to another user's ID
- Change the role from "user" to "admin"
- Remove the authorisation header entirely
- Add unexpected fields to the JSON body
Observe what the server accepts and rejects.
3. Map your own application
Draw a diagram of your application's entry points. For each one, list:
- Authentication required? (Yes / No)
- Authorisation checked? (Role, ownership, none)
- Input validated? (Schema, type, range)
- Sensitive data in response? (PII, credentials, internal details)
The gaps in this diagram are where attackers will look first.
From attacker thinking to secure coding
The goal is not to become an attacker. It is to develop an intuition for where things break:
- When writing a new endpoint, ask "What would I try if I wanted to abuse this?"
- When reviewing a pull request, look for missing authorisation checks, not just code style
- When designing a feature, consider the unhappy paths, not just the happy path
- When something feels "probably fine," that is exactly where vulnerabilities hide
Summary
Attackers think in entry points, assumptions, and chains. They enumerate everything, manipulate every parameter, and escalate from low-value to high-value access. To defend effectively, apply the same thinking to your own code: map your attack surface, hunt for broken assumptions, and walk through STRIDE for every endpoint. The best time to think like an attacker is while you are still writing the code.
