squirrelscan
GitHub

Version-revealing headers: why Server and X-Powered-By should not name a version

A Server or X-Powered-By header with a version number tells a scanner which known vulnerabilities to try. squirrel reads five headers and flags each version it finds.

What version disclosure is

Some responses name the software behind them. Server: Apache/2.4.41 (Ubuntu), X-Powered-By: PHP/7.4.3 and X-AspNet-Version: 4.0.30319 each put an exact version in front of every visitor. A scanner reads that and tries the vulnerabilities that version is known to have.

The header does not make the site insecure by itself, and a product name without a version is harmless (Server: nginx). The version is the part to remove, because it turns a generic probe into a targeted one. The fix is usually one line in the server or framework config.

What squirrel checks

The rule reads five headers on every crawled page: Server, X-Powered-By, X-AspNet-Version, X-AspNetMvc-Version and X-Generator. It emits one check named version-disclosure per distinct header value across the site.

  • Warn. A header value contains a dotted version number, at least major.minor. Message: {header} header reveals a software version: {value}, naming the header and the value (cut at 120 characters; the full value is kept for machine readers). The check’s item lists every page the value was seen on.
  • Not reported. A product name with no version, such as Server: nginx, Server: cloudflare or X-Powered-By: Express.
  • Not reported. A single number with no dot, such as X-Generator: Drupal 9. The pattern is strict on purpose, so a short value does not raise a false alarm.
  • Pass. No header on any crawled page names a version.
  • Skipped. The crawl produced no pages.
  • Skipped, older crawl. Page records stored before squirrel recorded X-Powered-By, X-AspNet-Version, X-AspNetMvc-Version and X-Generator cannot show those headers were absent, so the rule does not pass on them. Check name version-disclosure-unrecorded, message X-Powered-By, X-AspNet-Version, X-AspNetMvc-Version, X-Generator were not checked: this crawl's page records predate them. Re-run the audit with --refresh to check them. A version in Server, which was always recorded, is still reported.

Two pages that report the same Server value produce one finding, not two. Two different values for the same header produce one finding each.

How to fix it

Remove the version from the header, or the header entirely:

# nginx
server_tokens off;

# Apache
ServerTokens Prod
ServerSignature Off

# PHP (php.ini)
expose_php = Off

For ASP.NET, turn off the version headers in web.config (<httpRuntime enableVersionHeader="false" />) and set MvcHandler.DisableMvcResponseHeader = true in Global.asax. Remove X-Powered-By and X-Generator at the framework or at the reverse proxy; most frameworks have a single setting for each.

Check the result with curl -sI https://example.com | grep -i -E 'server|powered|aspnet|generator' after the change.

Rule IDsecurity/version-disclosure
CategorySecurity
ScopeSite-wide
Severitywarning
Weight3/10

Enable / disable

Disable this rule

[rules]
disable = ["security/version-disclosure"]

Disable all Security rules

[rules]
disable = ["security/*"]

Enable only this rule

[rules]
enable = ["security/version-disclosure"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

Security findings ship in every audit next to the SEO, performance and agent experience rules. See Website security scan with AI for how an agent works through a report.

References

Check your site

Run squirrel audit https://example.com and open the Security section of the report. Each distinct value is listed once, with the pages it appeared on. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close