Tech Stack Fingerprinting
Knowing it's Spring Boot 2.5 → CVE-2022-22965 candidate. Knowing it's "Java" → useless.
When to invoke
Trigger phrases:
- "what's running on X"
- "fingerprint this host"
- "tech stack of target"
- "version of framework"
What we identify
| Layer |
Signals |
Tools |
| Web server |
Nginx, Apache, IIS, Caddy |
httpx, nmap, headers |
| Backend lang |
Node, Python, Java, .NET, Go, PHP, Ruby |
headers, cookies, errors |
| Framework |
Express, Django, Spring, Rails, Laravel, FastAPI |
error pages, default routes |
| CMS |
WordPress, Drupal, Joomla, Ghost |
paths, scripts, meta |
| Frontend |
React, Vue, Angular, Svelte, Next.js |
JS files, bundle structure |
| API style |
REST, GraphQL, gRPC, SOAP |
endpoints, schemas |
| Cloud provider |
AWS, GCP, Azure, Cloudflare, Fastly |
DNS, headers, error pages |
| WAF |
Cloudflare, Akamai, Imperva, AWS WAF |
headers, behavior |
| Dependencies |
npm, JS libraries, CDN versions |
retire.js |
Step-by-Step Workflow
1. Quick scan with httpx
# tech-detect uses Wappalyzer logic
echo "https://app.target.com" | httpx -tech-detect -title -web-server -status-code -json | jq .
# Bulk on alive hosts:
cat live.txt | httpx -tech-detect -web-server -json | tee tech-detected.jsonl
Output sample:
{
"url": "https://app.target.com",
"tech": ["React", "Next.js:13.4", "Cloudflare", "Webpack"],
"webserver": "cloudflare",
"title": "Target App"
}
2. Deep scan with WhatWeb
# WhatWeb has wider plugin DB than httpx
whatweb -v --color=never -a 3 "https://app.target.com"
# Aggression levels:
# 1 = passive (just GET /)
# 3 = standard (default)
# 4 = heavy (lots of requests)
3. Header forensics
# Manual but precise:
curl -sI "https://app.target.com" | grep -iE "server|x-powered-by|x-aspnet|x-runtime|via|cf-ray|x-amz-|x-azure-"
# Examples of what to look for:
# X-Powered-By: PHP/7.4.3
# X-AspNet-Version: 4.0.30319
# X-Runtime: 0.045821 → Ruby on Rails
# Server: nginx/1.18.0 (Ubuntu)
# Via: 1.1 vegur → Heroku
# X-Vercel-Id → Vercel hosting
# X-Amz-Cf-Id → AWS CloudFront
4. Cookie fingerprinting
curl -sI "https://app.target.com" | grep -i "set-cookie"
# Cookie names reveal stack:
# PHPSESSID → PHP
# JSESSIONID → Java
# .AspNet.ApplicationCookie → ASP.NET
# _csrf, _session → Rails
# laravel_session → Laravel
# connect.sid → Express
# session → Flask/generic
# next-auth.session-token → Next.js + NextAuth
5. Error page fingerprinting
# Common error-page paths to fingerprint stack
for path in "/foo/bar" "/api/random-uuid" "/404-this-doesnt-exist" "/.env" "/server-status"; do
echo "=== $path ==="
curl -s "https://app.target.com$path" | head -30
echo
done
# Look for:
# - "Whitelabel Error Page" → Spring Boot
# - "Application Error" + heroku → Heroku app
# - "Cannot GET /foo" → Express
# - "DEBUG = True" Django stack trace → DEBUG mode (CRITICAL bug!)
# - "Internal Server Error" + Werkzeug → Flask
6. Frontend stack from JS bundle
# Get JS file URLs
curl -s "https://app.target.com" | grep -oE 'src="[^"]+\.js"' | sed 's/src="//;s/"$//'
# Check for framework fingerprints:
# react-dom.production.min.js → React
# vue.runtime.min.js → Vue
# @angular/core → Angular
# next/router → Next.js
# nuxt → Nuxt
# svelte → Svelte
# remix-run → Remix
# retire.js — find outdated JS libs
retire --jsrepo https://retirejs.github.io/retire.js/repository/jsrepo.json --js https://app.target.com
7. Nuclei tech-detect templates
# nuclei has hundreds of tech-detect templates
nuclei -u "https://app.target.com" -tags tech -silent
# Targeted: known versionable software
nuclei -u "https://app.target.com" -t http/technologies/ -silent
# Look for version disclosure
nuclei -u "https://app.target.com" -tags version -silent
8. Cloud / CDN detection
# DNS shows CNAME → CDN/provider
dig +short "app.target.com"
# Common CNAMEs:
# *.cloudfront.net → AWS CloudFront
# *.azureedge.net → Azure CDN
# *.fastly.net → Fastly
# *.cloudflare.com → Cloudflare (often via A record + IPs in CF range)
# *.ngrok.io → ngrok (developer tunnel — interesting!)
# *.herokuapp.com → Heroku
# *.vercel.app → Vercel
# *.netlify.app → Netlify
# *.s3.amazonaws.com → S3 bucket (check ownership = takeover candidate)
# *.github.io → GitHub Pages (takeover candidate)
9. WAF detection
# wafw00f
pip install wafw00f
wafw00f "https://app.target.com"
# Manual:
# - 403 with "challenge" page → Cloudflare
# - 403 + cf-ray header → Cloudflare
# - 403 + akamai-x-cache header → Akamai
# - 406 + X-Iinfo → Imperva
# - 403 + X-Sucuri-ID → Sucuri
Output template
target: app.target.com
fingerprint_date: 2026-06-02
infrastructure:
cdn: Cloudflare
ip: 104.21.x.x (CF IP)
origin_ip: unknown (CF protected)
waf: Cloudflare WAF
web_server:
product: nginx
version: 1.18 (inferred from defaults)
os: Ubuntu 20.04 LTS
backend:
language: Node.js
framework: Next.js 13.4
evidence: |
- X-Powered-By: Next.js
- JS bundle: _next/static/
- Headers: x-vercel-id (suggests Vercel hosting backed by Next.js)
frontend:
framework: React 18.2
bundler: Webpack 5
state_mgmt: Redux Toolkit (from JS analysis)
api:
style: REST + GraphQL
rest_base: /api/v3
graphql_endpoint: /api/graphql
evidence: discovered in main.js bundle
authentication:
type: NextAuth.js with custom OAuth provider
cookie: next-auth.session-token
jwt: yes (verified via JWT decoding)
cms: none
interesting_tech:
- GraphQL endpoint exposed (introspection?)
- NextAuth handles session — check JWT signing
- Vercel serverless — different attack surface (cold starts, env vars)
- WebSocket on /api/ws — possible RT data exchange
Tech → vuln class lookup
Use this to decide what to hunt next:
| Tech Found |
Try These Skills |
| WordPress |
nuclei wordpress templates, wpscan, REST API authcheck |
| GraphQL |
[[graphql]] — introspection, batching, depth |
| JWT in cookies |
[[jwt-attacks]] — alg=none, key confusion |
| OAuth visible |
[[oauth-oidc]] — redirect_uri, state |
| File upload UI |
[[file-upload]] |
| Multi-tenant URLs |
[[idor-hunting]] |
| Search/filter inputs |
[[sqli]], [[ssti]] |
| URL fetcher / webhook |
[[ssrf]] |
| Spring Boot |
CVE checks (RCE history), actuator endpoints |
| Old Drupal |
CVE-2018-7600 Drupalgeddon, etc. |
| Old WordPress plugins |
nuclei wordpress-CVEs templates |
| Outdated jQuery |
retire.js → CVEs |
| Exposed Swagger/OpenAPI |
API enumeration goldmine |
Cross-references
[[asset-discovery]] — runs before this
[[js-analysis]] — deeper JS endpoint mining
[[content-discovery]] — fuzz paths based on tech
[[nuclei]]-style scanning — match templates to fingerprinted stack
Common pitfalls
- Trusting
Server: header alone. It's often reverse-proxy, not the actual app.
- Missing JS-based SPA detection. SPA = different attack surface.
- Not checking all origin endpoints. Same domain may have multiple stacks behind path-based routing.
- Treating Cloudflare as the app. WAF can mislead — try origin IP if findable.
- Skipping the error pages. Stack traces > Wappalyzer fingerprints any day.
Pro tips
- Bring up devtools in the browser. Network tab shows headers + cookies in seconds.
- Note
_next/, __nuxt/, _remix/ paths. They signal SSR frameworks → unique bug classes.
- Save fingerprint to
loot/<target>/fingerprint.yaml. Re-use for re-tests.
- Run fingerprinting weekly on active targets. Tech stack updates create new attack windows.
1---2name: fingerprinting3description: Identify exact technology stack, frameworks, versions, and CMS on live hosts. Use when the user has a live host list and needs to know what's running before choosing vuln classes to test.4---5
6# Tech Stack Fingerprinting
7
8> Knowing it's Spring Boot 2.5 → CVE-2022-22965 candidate. Knowing it's "Java" → useless.
9
10## When to invoke
11
12**Trigger phrases:**
13- "what's running on X"
14- "fingerprint this host"
15- "tech stack of target"
16- "version of framework"
17
18## What we identify
19
20| Layer | Signals | Tools |
21|---|---|---|
22| Web server | Nginx, Apache, IIS, Caddy | httpx, nmap, headers |
23| Backend lang | Node, Python, Java, .NET, Go, PHP, Ruby | headers, cookies, errors |
24| Framework | Express, Django, Spring, Rails, Laravel, FastAPI | error pages, default routes |
25| CMS | WordPress, Drupal, Joomla, Ghost | paths, scripts, meta |
26| Frontend | React, Vue, Angular, Svelte, Next.js | JS files, bundle structure |
27| API style | REST, GraphQL, gRPC, SOAP | endpoints, schemas |
28| Cloud provider | AWS, GCP, Azure, Cloudflare, Fastly | DNS, headers, error pages |
29| WAF | Cloudflare, Akamai, Imperva, AWS WAF | headers, behavior |
30| Dependencies | npm, JS libraries, CDN versions | retire.js |
31
32## Step-by-Step Workflow
33
34### 1. Quick scan with httpx
35
36```bash
37# tech-detect uses Wappalyzer logic
38echo "https://app.target.com" | httpx -tech-detect -title -web-server -status-code -json | jq .
39
40# Bulk on alive hosts:
41cat live.txt | httpx -tech-detect -web-server -json | tee tech-detected.jsonl
42```
43
44Output sample:
45```json
46{
47 "url": "https://app.target.com",
48 "tech": ["React", "Next.js:13.4", "Cloudflare", "Webpack"],
49 "webserver": "cloudflare",
50 "title": "Target App"
51}
52```
53
54### 2. Deep scan with WhatWeb
55
56```bash
57# WhatWeb has wider plugin DB than httpx
58whatweb -v --color=never -a 3 "https://app.target.com"
59
60# Aggression levels:
61# 1 = passive (just GET /)
62# 3 = standard (default)
63# 4 = heavy (lots of requests)
64```
65
66### 3. Header forensics
67
68```bash
69# Manual but precise:
70curl -sI "https://app.target.com" | grep -iE "server|x-powered-by|x-aspnet|x-runtime|via|cf-ray|x-amz-|x-azure-"
71
72# Examples of what to look for:
73# X-Powered-By: PHP/7.4.3
74# X-AspNet-Version: 4.0.30319
75# X-Runtime: 0.045821 → Ruby on Rails
76# Server: nginx/1.18.0 (Ubuntu)
77# Via: 1.1 vegur → Heroku
78# X-Vercel-Id → Vercel hosting
79# X-Amz-Cf-Id → AWS CloudFront
80```
81
82### 4. Cookie fingerprinting
83
84```bash
85curl -sI "https://app.target.com" | grep -i "set-cookie"
86
87# Cookie names reveal stack:
88# PHPSESSID → PHP
89# JSESSIONID → Java
90# .AspNet.ApplicationCookie → ASP.NET
91# _csrf, _session → Rails
92# laravel_session → Laravel
93# connect.sid → Express
94# session → Flask/generic
95# next-auth.session-token → Next.js + NextAuth
96```
97
98### 5. Error page fingerprinting
99
100```bash
101# Common error-page paths to fingerprint stack
102for path in "/foo/bar" "/api/random-uuid" "/404-this-doesnt-exist" "/.env" "/server-status"; do
103 echo "=== $path ==="
104 curl -s "https://app.target.com$path" | head -30
105 echo
106done
107
108# Look for:
109# - "Whitelabel Error Page" → Spring Boot
110# - "Application Error" + heroku → Heroku app
111# - "Cannot GET /foo" → Express
112# - "DEBUG = True" Django stack trace → DEBUG mode (CRITICAL bug!)
113# - "Internal Server Error" + Werkzeug → Flask
114```
115
116### 6. Frontend stack from JS bundle
117
118```bash
119# Get JS file URLs
120curl -s "https://app.target.com" | grep -oE 'src="[^"]+\.js"' | sed 's/src="//;s/"$//'
121
122# Check for framework fingerprints:
123# react-dom.production.min.js → React
124# vue.runtime.min.js → Vue
125# @angular/core → Angular
126# next/router → Next.js
127# nuxt → Nuxt
128# svelte → Svelte
129# remix-run → Remix
130
131# retire.js — find outdated JS libs
132retire --jsrepo https://retirejs.github.io/retire.js/repository/jsrepo.json --js https://app.target.com
133```
134
135### 7. Nuclei tech-detect templates
136
137```bash
138# nuclei has hundreds of tech-detect templates
139nuclei -u "https://app.target.com" -tags tech -silent
140
141# Targeted: known versionable software
142nuclei -u "https://app.target.com" -t http/technologies/ -silent
143
144# Look for version disclosure
145nuclei -u "https://app.target.com" -tags version -silent
146```
147
148### 8. Cloud / CDN detection
149
150```bash
151# DNS shows CNAME → CDN/provider
152dig +short "app.target.com"
153
154# Common CNAMEs:
155# *.cloudfront.net → AWS CloudFront
156# *.azureedge.net → Azure CDN
157# *.fastly.net → Fastly
158# *.cloudflare.com → Cloudflare (often via A record + IPs in CF range)
159# *.ngrok.io → ngrok (developer tunnel — interesting!)
160# *.herokuapp.com → Heroku
161# *.vercel.app → Vercel
162# *.netlify.app → Netlify
163# *.s3.amazonaws.com → S3 bucket (check ownership = takeover candidate)
164# *.github.io → GitHub Pages (takeover candidate)
165```
166
167### 9. WAF detection
168
169```bash
170# wafw00f
171pip install wafw00f
172wafw00f "https://app.target.com"
173
174# Manual:
175# - 403 with "challenge" page → Cloudflare
176# - 403 + cf-ray header → Cloudflare
177# - 403 + akamai-x-cache header → Akamai
178# - 406 + X-Iinfo → Imperva
179# - 403 + X-Sucuri-ID → Sucuri
180```
181
182## Output template
183
184```yaml
185target: app.target.com
186fingerprint_date: 2026-06-02
187
188infrastructure:
189 cdn: Cloudflare
190 ip: 104.21.x.x (CF IP)
191 origin_ip: unknown (CF protected)
192 waf: Cloudflare WAF
193
194web_server:
195 product: nginx
196 version: 1.18 (inferred from defaults)
197 os: Ubuntu 20.04 LTS
198
199backend:
200 language: Node.js
201 framework: Next.js 13.4
202 evidence: |
203 - X-Powered-By: Next.js
204 - JS bundle: _next/static/
205 - Headers: x-vercel-id (suggests Vercel hosting backed by Next.js)
206
207frontend:
208 framework: React 18.2
209 bundler: Webpack 5
210 state_mgmt: Redux Toolkit (from JS analysis)
211
212api:
213 style: REST + GraphQL
214 rest_base: /api/v3
215 graphql_endpoint: /api/graphql
216 evidence: discovered in main.js bundle
217
218authentication:
219 type: NextAuth.js with custom OAuth provider
220 cookie: next-auth.session-token
221 jwt: yes (verified via JWT decoding)
222
223cms: none
224
225interesting_tech:
226 - GraphQL endpoint exposed (introspection?)
227 - NextAuth handles session — check JWT signing
228 - Vercel serverless — different attack surface (cold starts, env vars)
229 - WebSocket on /api/ws — possible RT data exchange
230```
231
232## Tech → vuln class lookup
233
234Use this to decide what to hunt next:
235
236| Tech Found | Try These Skills |
237|---|---|
238| WordPress | nuclei wordpress templates, wpscan, REST API authcheck |
239| GraphQL | `[[graphql]]` — introspection, batching, depth |
240| JWT in cookies | `[[jwt-attacks]]` — alg=none, key confusion |
241| OAuth visible | `[[oauth-oidc]]` — redirect_uri, state |
242| File upload UI | `[[file-upload]]` |
243| Multi-tenant URLs | `[[idor-hunting]]` |
244| Search/filter inputs | `[[sqli]]`, `[[ssti]]` |
245| URL fetcher / webhook | `[[ssrf]]` |
246| Spring Boot | CVE checks (RCE history), actuator endpoints |
247| Old Drupal | CVE-2018-7600 Drupalgeddon, etc. |
248| Old WordPress plugins | nuclei wordpress-CVEs templates |
249| Outdated jQuery | retire.js → CVEs |
250| Exposed Swagger/OpenAPI | API enumeration goldmine |
251
252## Cross-references
253
254- `[[asset-discovery]]` — runs before this
255- `[[js-analysis]]` — deeper JS endpoint mining
256- `[[content-discovery]]` — fuzz paths based on tech
257- `[[nuclei]]`-style scanning — match templates to fingerprinted stack
258
259## Common pitfalls
260
2611. **Trusting `Server:` header alone.** It's often reverse-proxy, not the actual app.
2622. **Missing JS-based SPA detection.** SPA = different attack surface.
2633. **Not checking *all* origin endpoints.** Same domain may have multiple stacks behind path-based routing.
2644. **Treating Cloudflare as the app.** WAF can mislead — try origin IP if findable.
2655. **Skipping the error pages.** Stack traces > Wappalyzer fingerprints any day.
266
267## Pro tips
268
269- **Bring up devtools in the browser.** Network tab shows headers + cookies in seconds.
270- **Note `_next/`, `__nuxt/`, `_remix/` paths.** They signal SSR frameworks → unique bug classes.
271- **Save fingerprint to `loot/<target>/fingerprint.yaml`.** Re-use for re-tests.
272- **Run fingerprinting weekly on active targets.** Tech stack updates create new attack windows.