whatweb
Fingerprint web stacks with WhatWeb: CMS, frameworks, servers, JavaScript libraries and versions from response bodies, headers and favicons. Use during triage of a host list to pick which targets dese
SKILL.mdskills/whatweb/SKILL.md
GitHubwhatweb
WhatWeb sends one request per target at its default aggression and matches 1800+ Ruby plugins against the response. It is a triage layer, not a scanner: its job is to turn 10.0.0.0/24 into "these four run WordPress, that one is a GitLab, this one is an empty nginx" so you know where to spend requests. Version on this host is 0.5.5, plugins in /usr/share/whatweb/plugins.
When to use
| Goal | Invocation |
|---|---|
| One-line summary per host | whatweb --no-errors target |
| Exact version of a known CMS | whatweb -a 3 target |
| Fingerprint a whole subnet | whatweb --no-errors --url-prefix https:// 10.0.0.0/24 |
| Feed a scanner or a report | whatweb -i hosts.txt --log-json out.json |
| Only specific plugins | whatweb -p title,wordpress,php target |
| Discover plugins for a product | whatweb --info-plugins=wordpress |
| Grep results for a pattern | whatweb --grep '/nginx[\/ ]?[0-9.]+/' target |
Do not use it for directory brute force (ffuf), TLS/certificate inventory (openssl s_client, testssl.sh), or anything requiring authentication beyond a simple cookie or basic auth header.
Prerequisites
whatweb --versionprintsWhatWeb version 0.5.5.- Ruby is required at runtime; the package ships its own plugin tree, so no gem install is needed.
- Targets must be reachable from this host, or through
--proxy. - For authenticated triage, a session cookie (
--cookie) or basic credentials (-u user:pass) is enough; anything more complex needs a real browser.
Core invocations
# 1. default aggression: one request, redirects followed, compact one-liner
whatweb --no-errors 10.0.0.5
# 2. aggressive: if a level-1 plugin matches, ask follow-up questions (version, readme, login page)
whatweb -a 3 10.0.0.5
# 3. heavy: tries every URL every plugin knows about; slow and very loud
whatweb -a 4 10.0.0.5
# 4. whole subnet over https, errors suppressed, 25 threads
whatweb --no-errors --url-prefix https:// 10.0.0.0/24
# 5. from a file, one target per line, JSON for later diffing
whatweb -i hosts.txt --no-errors --log-json /tmp/fingerprint.json
# 6. rate-limited sweep for a sensitive range: 5 threads, 1s between connections
whatweb -t 5 --wait 1 --no-errors -i hosts.txt
# 7. present as a normal browser instead of WhatWeb/0.5.5
whatweb --user-agent 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/126 Safari/537.36' 10.0.0.5
# 8. limit to cheap, deterministic plugins
whatweb -p title,ip,country,httpserver,x-powered-by --no-errors 10.0.0.5
# 9. authenticated triage with a session cookie
whatweb --cookie 'PHPSESSID=deadbeef; wordpress_logged_in_x=deadbeef' 10.0.0.5
# 10. do not follow redirects, so the 301 target stays visible
whatweb --follow-redirect=never 10.0.0.5
# 11. what does a plugin detect, and how?
whatweb --info-plugins=wordpress | head -20
whatweb --search-plugins=jira
# 12. filter output without post-processing
whatweb --grep 'WordPress' -i hosts.txt
# 13. verbose plugin descriptions for a single host
whatweb -v 10.0.0.5
# 14. keep both a readable and a machine log in the same run
whatweb -a 3 --log-brief /tmp/brief.txt --log-json /tmp/full.json 10.0.0.5
Flags that matter
| Flag | Effect | Note |
|---|---|---|
-a 1 |
one request per target, follows redirects | default; fastest, least detail |
-a 3 |
extra requests once a level-1 plugin matches | the useful default for a single host |
-a 4 |
every URL from every plugin | dozens of requests per target; use on one host, not a /24 |
-t <n> |
threads, default 25 | 5-10 for fragile or monitored targets |
--wait <s> |
pause between connections | only meaningful with low thread counts |
-i <file> |
target list, /dev/stdin works |
one URL/host per line |
--url-prefix |
prepend to each target | fastest way to force https on a CIDR |
--url-suffix / --url-pattern |
append or template a path | --url-suffix /robots.txt |
-p <list> |
plugin selection, +/- modifiers |
-p +plugins-disabled re-enables disabled ones |
-g <string|/regex/> |
show only matching results | quoting matters in sh |
-U / --user-agent |
override the UA | default WhatWeb/0.5.5 is a scanner signature |
-H |
add or replace a header | -H 'X-Forwarded-For: 127.0.0.1' |
-c / --cookie |
cookie header | combine with --cookie-jar for real sessions |
-u user:pass |
HTTP basic auth | not form login |
--proxy host:port |
egress through a proxy | plus --proxy-user |
--open-timeout / --read-timeout |
15s / 30s defaults | lower them on a broken range |
--log-json / --log-xml / --log-brief |
structured output | - writes to stdout and interleaves with the brief line |
-q / --no-errors |
less noise | --no-errors is the one you want on a /24 |
--max-redirects |
default 10 | lower to spot chains |
-l, -I, --search-plugins |
list and inspect plugins | offline, no traffic |
Workflow
- Sweep cheap.
-a 1 --no-errors --url-prefix https://over the range, JSON to a file. Expect the first pass to be mostlyHTTPServerandIP. - Read the one-liners.
Title,MetaGenerator,HTTPServerandRedirectLocationusually separate the interesting hosts from the empty ones. - Escalate per host.
-a 3on the handful that matched a CMS or framework; that is where versions appear. - Confirm with a second plugin set.
-a 4or a targeted-p wordpress,...beats trusting one heuristic match. - Diff two runs. JSON in,
jqacross time, to see what changed after an application update. - Hand off. Feed the CMS/version pairs into
nuclei -tags, and the live hosts intoffuf.
OPSEC and telemetry
- Default
User-AgentisWhatWeb/0.5.5: any WAF or log review sees the scanner name. Override it or accept being blocked. -a 3and-a 4multiply requests per target;-a 4on a /24 is thousands of requests and is a reliable way to get banned mid-engagement.- Redirects are followed by default, so a scan can silently drift to a CDN or an external login portal;
--follow-redirect=neverkeeps the target list honest. - WhatWeb evaluates only server responses; it does not run JavaScript, so SPA-only stacks look empty.
- The JSON log records full URLs and any cookies you supplied. Treat it as sensitive output.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
Every line is IP[...] and nothing else |
site behind a WAF, or https-only with a bad cert | --url-prefix https://, -a 3, and consider --user-agent |
[ERROR] ... SSL_connect on a whole range |
expired or self-signed certificates | --no-errors, or accept the noise and read the rest |
| A single host hangs the run | no --open-timeout/--read-timeout tuning |
--open-timeout 5 --read-timeout 10 |
| Versions never appear | aggression 1 only reads one response | -a 3 (or -a 4 on that host) |
| Output and JSON are mangled together | --log-json - writes to stdout beside the brief line |
log to a real file |
Nothing matches with -p |
plugin name wrong or plugin disabled | `-l |
| Ruby exception / stack trace | a plugin crashed on odd input | --no-errors to continue, --debug to see it |
| Results change every run | random chain through a proxy, or load-balanced targets | pin -t 1 and compare full responses |
| CIDR scan hits only port 80 | --url-prefix was not set and http was assumed |
set --url-prefix https:// explicitly |
Verification
whatweb --version # WhatWeb version 0.5.5 ( https://www.morningstarsecurity.com/research/whatweb/ )
whatweb -l | wc -l # 1835 lines including the header
whatweb --no-errors 127.0.0.1
# http://127.0.0.1 [308 Permanent Redirect] Country[RESERVED][ZZ], HTTPServer[Caddy], IP[127.0.0.1], RedirectLocation[https://127.0.0.1/]
whatweb -a 3 --user-agent 'Mozilla/5.0 (X11; Linux x86_64)' --log-json /tmp/ww.json 127.0.0.1
cat /tmp/ww.json
# [{"target":"http://127.0.0.1","http_status":308,
# "request_config":{"headers":{"User-Agent":"Mozilla/5.0 (X11; Linux x86_64)"}},
# "plugins":{"Country":{"string":["RESERVED"],"module":["ZZ"]},
# "HTTPServer":{"string":["Caddy"]},"IP":{"string":["127.0.0.1"]},
# "RedirectLocation":{"string":["https://127.0.0.1/"]}}}]
A JSON line per target, with plugins keyed by plugin name, is the success shape. Empty plugins means the response matched nothing, not that the host is down.
Related skills
- httpx for status/title/tech bulk probing before whatweb
- ffuf for content discovery once the stack is known
- nuclei to turn a version into a template set
- wordlists for the lists behind those follow-ups
- toolchain-router to pick a different fingerprinting tool