ankur@sec:~$

$ cat attack-surface.md

What is attack surface?

Your attack surface is every place where someone outside can send something into your system, or learn something about it.

As a developer I thought of my apps as features. As a pentester I see them as doors. A login form is a door. So is a query parameter, an open port, a response header, a forgotten subdomain, and the 1,200 packages in node_modules.

Security doesn't start with making every door unbreakable. It starts with a simpler question: how many doors do you have - and do you know about all of them?

## known, unknown, forgotten

known

The production app, its API, the login page. You test these. Attackers expect them to be hardened.

forgotten

The v1 API, the staging server, the demo for a client in 2022. Still running, never patched. This is where most real findings are.

unknown

Things nobody on the team knows exist: a marketing tool on a subdomain, a bucket made by a contractor, a key in an old commit.

Attackers don't care which category a door is in. They find all three the same way - with OSINT - and they usually start with the ones you forgot.

## the doors in a typical web app

layerdoorsleads to
Domains & subdomainswww, api, staging, old-admin, the marketing site an agency built in 2019A02
Endpoints & parametersEvery route and every input - including v1 APIs nobody turned offA01
Ports & servicesSSH, databases, admin panels, debug ports, metrics endpointsA02
What you revealVersion headers, stack traces, Swagger pages, source maps, .git foldersA10
Code you didn't writenpm / NuGet packages, base images, CI actions - and their version tagsA03
Secrets & codePublic repos, old commits, JS bundles with API keysA04
PeopleEmployee names, email formats, reused passwords, phishable inboxesA07

## two kinds of door

things you can reach

Endpoints, ports, forms, uploads. Each one accepts input, and input is where injection, access-control bugs and RCE start.

things you reveal

Version headers, stack traces, API schemas, subdomains in certificates. They don't break anything alone - they tell an attacker where to look.

## how version tags grow your attack surface

This is the example that gets developers' attention most often, because it hits from two sides.

1 · showing your version

HTTP/1.1 200 OK
Server: nginx/1.18.0
X-Powered-By: ASP.NET
X-AspNet-Version: 4.0.30319

Those lines don't break anything. But nginx 1.18.0 goes straight into a CVE search, and the attacker gets a list of known vulnerabilities for your exact version - without sending a single malicious request. Shodan has usually indexed it already.

stackhide the version
nginxserver_tokens off;
Expressapp.disable('x-powered-by');
ASP.NET Core (Kestrel)options.AddServerHeader = false;
ASP.NET (IIS)<httpRuntime enableVersionHeader="false" /> + remove X-Powered-By

Hiding the version is not patching. It removes the free hint - you still need to update.

# Check your own app

$ curl -sI https://example.com | grep -iE '^(server|x-powered-by|x-aspnet-version|x-aspnetmvc-version):'

2 · not pinning your version

// package.json
"express": "^4.18.0"     // any 4.x ≥ 4.18.0

# Dockerfile
FROM node:latest

# GitHub Actions
- uses: some-org/some-action@v2

Every highlighted value is a moving target. If a maintainer account is compromised and a malicious version is published, your next build pulls it in - no pull request, no review, nothing in your git diff. That code is now part of your attack surface, and you never chose it.

what I did as a developer

I set up Watchtower to automatically pull updated images and restart containers. It felt like great automation - always up to date, zero manual work.

Looking at it now as a pentester: any new image published under that tag went straight to production, with nobody reviewing what changed. Convenient for me - and just as convenient for an attacker.

whereloosepinned
npm^4.18.0 + npm installcommitted lockfile + npm ci
NuGet13.*exact versions + packages.lock.json
Dockernode:latestnode:22-alpine@sha256:…
GitHub Actions@v2@<full commit SHA> # v2.3.1

Pinning doesn't mean never updating. It means updates arrive as a reviewed change - a Dependabot or Renovate pull request you can see, scan and approve. More in A03 Software Supply Chain Failures.

## shrinking it

  1. Inventory. List every domain, subdomain, app and API you own. Recon yourself to find the ones missing from the list.
  2. Delete. The safest door is one that doesn't exist. Turn off old APIs, staging servers and demo apps.
  3. Hide. Remove version banners, Swagger UI, source maps and stack traces from production.
  4. Pin. Lock dependencies, images and CI actions to exact versions.
  5. Repeat. Attack surface grows with every deploy. Re-run the recon regularly.

→ next: how attackers map your attack surface with OSINT