When a security review starts with "please send us your architecture diagram", what the reviewer really wants is the attack surface: every place where data or people from outside can reach the application. Most teams have never written that list down. They know the main pages and the public API, and forget the export endpoint added for one customer, the debug route that survived a release, or the webhook from a partner nobody remembers configuring.
In this post I walk through how we map the attack surface of a typical web application, area by area, and the control we expect to find in each. It takes a team about two hours for a medium-sized app, and it is the most useful two hours of any security review I run.
What counts as attack surface
The attack surface is the sum of all points where an untrusted party can send data into the system, read data out of it, or influence how it behaves. For a web application that includes:
- every route and API endpoint, including internal and deprecated ones;
- every input on those routes: path, query, headers, cookies, body, uploaded files;
- authentication and session mechanisms;
- data the application fetches from elsewhere: URLs, webhooks, imports, third-party APIs;
- the people and tools with privileged access: admin panels, support tools, CI pipelines;
- client-side code and the third-party scripts it loads.
The goal is not a perfect inventory. It is a list good enough that nothing important is invisible.
Step 1: list the entry points from the code, not from memory
Start with what the framework knows. Most frameworks can print their routes:
php artisan route:list --json > routes.json # Laravel
python manage.py show_urls > routes.txt # Django with django-extensions
npx express-list-endpoints app.js # Express, via a small helper package
Then add what the router does not show: scheduled jobs that fetch remote data, queue consumers, webhook receivers on separate services, and any serverless function behind the same domain. For each entry point, record four facts:
| Route | Auth required | Who can call it | Data it touches |
|---|---|---|---|
POST /login |
No | Anyone | Users, sessions |
GET /api/invoices/{id} |
Yes | Customers | Invoices of one account |
POST /webhooks/payments |
Signature | Payment provider | Orders, payments |
GET /admin/users/export |
Yes, admin | Staff | All users, PII |
GET /debug/phpinfo |
No | Anyone | Server configuration |
The last row is the kind of thing this exercise is for. Delete it.
Step 2: go area by area
Inputs
Every parameter that reaches a query, a template, a shell command, a file path or another service is a potential injection point.
Control we expect: validation by type and allow-list at the boundary, parameterised queries, automatic output encoding in templates, and no string-built commands. Check sort and filter parameters specifically; they are often passed directly into queries.
Authentication
Login, registration, password reset, MFA enrolment, social login callbacks and API key handling.
Control we expect: rate limiting on all of them, uniform error messages that do not reveal whether an account exists, reset tokens that are single-use and expire, MFA for staff and privileged users, and validation of OAuth redirect URIs against an exact allow-list.
Sessions
Cookies or tokens that carry the authenticated state.
Control we expect: Secure, HttpOnly and an appropriate SameSite attribute on session cookies;
session rotation after login and privilege changes; server-side invalidation on logout and password
change; CSRF protection on state-changing requests when cookies are used.
Authorization
The most common serious finding in my reviews. Every endpoint that takes an object ID must check that the caller may access that object.
Control we expect: a policy check on every object access, queries scoped to the caller's tenant or account, and tests that call endpoints as the wrong user and expect a 403 or 404.
File uploads and downloads
Avatars, attachments, imports, generated reports.
Control we expect: size limits, content-type validation based on file content rather than the name, storage outside the web root or in object storage with private access, randomised file names, malware scanning for files other users will open, and authorization checks on downloads.
Outbound requests
Any feature where the server fetches a URL supplied or influenced by a user: link previews, webhooks you send to customer URLs, image imports.
Control we expect: an allow-list of destinations where possible, blocking of internal and metadata address ranges, no following of redirects to internal hosts, and timeouts. Cloud instance metadata endpoints in particular must not be reachable from user-controlled requests.
APIs
Public, partner and mobile APIs often grow faster than the web UI and get less review.
Control we expect: the same authorization as the UI, pagination and rate limits, responses that return only the fields the client needs, versioning so old insecure behaviour can be retired, and an inventory of keys with owners.
Admin and support tools
Back-office screens usually have the broadest access and the weakest review.
Control we expect: separate authentication with MFA, network restriction or single sign-on, role-based permissions inside the tool, audit logging of every view and change of customer data, and no shared accounts.
Third-party code and services
Client-side scripts (analytics, chat widgets, payment fields), server-side packages, and SaaS services that hold your data.
Control we expect: a content security policy that limits which script sources can run, subresource integrity for static third-party files, dependency scanning in CI, and a list of vendors with the data each receives.
Infrastructure edges
DNS records, subdomains, storage buckets, CDN configuration and exposed management ports.
Control we expect: an inventory of subdomains with owners, removal of records pointing to decommissioned services (which can be taken over), private storage by default, and periodic external scans of your own address ranges.
Step 3: rate each area
For each area, record whether the expected control exists, partly exists or is missing, and how much damage a failure would cause. Keep it simple:
| Area | Control status | Impact if it fails | Action |
|---|---|---|---|
| Authorization on object IDs | Partial: 3 endpoints unchecked | High | Fix this sprint, add tests |
| Password reset | OK | High | None |
| File downloads | Missing auth on /files/{id} |
High | Fix now |
| Outbound URL fetch | No internal range blocking | High | Fix this sprint |
| Admin tool | No MFA | High | Enable SSO with MFA |
| Third-party scripts | No CSP | Medium | Introduce CSP in report-only mode |
This table becomes the input for the team's backlog and for the risk register.
Step 4: keep it current
An attack surface map is out of date the day after it is written unless it is tied to the development process. Three habits keep it alive:
- New routes require an authorization decision in code review, and the reviewer checks it.
- A CI job compares the current route list with the previous release and posts new routes in the pull request, so additions are visible.
- The map is reviewed once per quarter, together with the external scan results.
Common mistakes
| Mistake | Why it hurts | Do this instead |
|---|---|---|
| Listing only documented endpoints | Forgotten routes are the ones attacked | Generate the list from the router and infrastructure |
| Treating internal APIs as out of scope | Lateral movement uses them | Map and protect them too |
| Testing authorization only as admin | Admin can see everything, so bugs stay hidden | Test as low-privilege and wrong-tenant users |
| Ignoring client-side scripts | Third-party script compromise reaches every user | CSP and an inventory of script sources |
| One-off review | The surface changes every release | Route diff in CI and quarterly review |
Key takeaways
- The attack surface is every place untrusted data or people reach your system, including forgotten routes and third parties.
- Generate the inventory from code and infrastructure, not memory.
- Each area has a known set of controls; the review is checking which exist.
- Authorization on object access is the most common high-impact gap.
- Tie the map to code review and CI so it stays current.
References
- OWASP Attack Surface Analysis Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Attack_Surface_Analysis_Cheat_Sheet.html
- OWASP Application Security Verification Standard: https://owasp.org/www-project-application-security-verification-standard/
- OWASP Top 10: https://owasp.org/www-project-top-ten/
- MDN, Content Security Policy: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
Comments
0No comments yet. Be the first to comment!
Sign In to post a comment.