Recon and mapping the target
Log in Join

Lesson 5 · Reading · 25 min

Fingerprinting the stack

Every application is built on a stack: a web server, a programming language, a framework, maybe a content-management system, running on an operating system. Fingerprinting is naming those pieces, and ideally their versions, from the tells they leave. It matters because a bug is rarely "a bug in websites", it is a bug in this framework, this version, and knowing the name turns a blind poke into a targeted search.

A single response leaks its stack through headers, cookies, generator tags and asset paths

Why the name is worth so much

Suppose you learn the target runs a specific version of a specific CMS. You can now do something powerful: look it up. Public vulnerability databases (the CVE list, exploit archives) will tell you every known flaw in that exact version. Half of real-world compromise is not clever new exploitation; it is finding software that is a few versions behind and using a weakness that was published months ago. Fingerprinting is what makes that possible, and it is exactly the skill an exam means when it says "identify the technologies a web application is using."

The tells, from loudest to subtlest

Headers. As the last lesson showed, Server, X-Powered-By, and framework-specific headers like X-Generator: Drupal 9 or X-AspNet-Version often name the stack outright. Always the first place to look.

Cookies. The session cookie's name is a reliable fingerprint even when headers are stripped: PHPSESSID, laravel_session, JSESSIONID, connect.sid, ASP.NET_SessionId, _django. The framework chose that name; the developer rarely changes it.

Paths and asset URLs. Frameworks and CMSes organise files predictably. /wp-login.php and /wp-content/ are WordPress. /administrator/ is Joomla. /sites/default/ is Drupal. /_next/static/ is Next.js. /static/admin/ with a certain login style is Django. You can often name a CMS from a single asset URL in the page source.

Favicon hashes. Many products ship a default favicon. Its hash is effectively a signature: tools compare the favicon's hash to a database of known products. A default favicon can name an appliance or admin tool that reveals nothing else.

Error pages and behaviour. Ask for a page that does not exist, or send a deliberately malformed request, and read the error. A stack trace names the framework and often the version and file paths. A default 404 has a recognisable look per server. The format of an error, JSON shape, HTML template, wording, is itself a fingerprint.

HTML meta and generator tags. Some sites still emit <meta name="generator" content="WordPress 6.2">. Free.

Doing it by hand, then with tools

Start by hand, because it teaches you what the tools are reading:

curl -sI https://example.com/            # headers, Server, X-Powered-By, Set-Cookie
curl -s  https://example.com/ | grep -i generator   # meta generator tag
curl -sI https://example.com/nonexistent-abc123     # the 404 behaviour

Then let a tool sweep the common tells at once. whatweb and httpx -tech-detect recognise hundreds of technologies from headers, cookies, bodies and favicons:

whatweb https://example.com

Browser extensions like Wappalyzer do the same live as you browse. Use tools to go fast, but always sanity-check: fingerprinters guess, and a stripped or spoofed header can mislead them.

From version to known weakness

Once you have a name and a version, the workflow is:

  1. Write down the exact product and version (Apache 2.4.49, WordPress 5.7, Log4j 2.14).
  2. Search public sources: the CVE database, the vendor's changelog (which often describes what a version fixed, i.e. what the previous version was vulnerable to), and exploit archives.
  3. Note which known issues plausibly apply to how the target is configured. A published vulnerability is a lead, not a guaranteed win, you still have to confirm it is present and reachable.

This is what "exploit vulnerable and outdated components" means in practice, and it starts with the fingerprint.

When the stack is quiet

Well-run targets strip headers, rename cookies, and use custom error pages precisely to frustrate this. Do not give up, fall back to the subtler tells. Timing and behaviour differ between stacks. Parameter names and URL structure leak the framework. The way the app handles an unexpected content-type or an oversized request differs. And even a target that hides its version still tells you its product through paths and favicons, which narrows the search enormously.

Keep it honest

Fingerprinting is mostly reading responses, which is quiet, but requesting error pages and malformed inputs is active traffic against the target. Same rule: in scope only. And finding that a target runs an old, vulnerable version is a real, reportable finding by itself on an authorised engagement, you often do not even need to exploit it, only to prove the version and cite the known issue.

Where this goes next

You know the neighbourhood, you have read what each app hands out, and you can name the stack. The next lesson goes after the pages the app did not hand out: content discovery, where a wordlist turns "the pages I can click" into "the pages that exist," and the unlinked ones are where so many findings live.

This lesson has 1 lab to try what you just read.