checker: flag information-disclosure response headers

Report Server, X-Powered-By, X-AspNet-Version and X-AspNetMvc-Version
headers, whose presence leaks the server/framework stack. Server only
warns on informative values (version/product detail), accepting a
non-informative value per OWASP.
This commit is contained in:
nemunaire 2026-06-18 10:59:54 +09:00
commit a0fb42223b
3 changed files with 145 additions and 0 deletions

View file

@ -90,6 +90,86 @@ func init() {
}}
},
}))
// Information-disclosure headers. Their mere presence leaks the
// server/framework stack, helping an attacker map known vulnerabilities.
// Absence is the desired state, so these invert the usual semantics:
// present → Info ".disclosed", absent → OK ".absent".
RegisterRule(HeaderRule(HeaderRuleSpec{
Code: "http.server_header",
Description: "Reports whether the Server header discloses the origin server software and version.",
Header: "Server",
Inspect: inspectServerHeader,
OnMissing: func(_ HTTPProbe, _ sdk.CheckerOptions) []HeaderResult {
return []HeaderResult{{
Status: sdk.StatusOK,
Suffix: "absent",
Message: "Server header is not set, so no origin server software is disclosed.",
}}
},
}))
RegisterRule(disclosureHeaderRule("http.x_powered_by", "X-Powered-By", "the technologies used by the web server"))
RegisterRule(disclosureHeaderRule("http.x_aspnet_version", "X-AspNet-Version", "the ASP.NET framework version"))
RegisterRule(disclosureHeaderRule("http.x_aspnetmvc_version", "X-AspNetMvc-Version", "the ASP.NET MVC version"))
}
// Information-disclosure headers --------------------------------------
// disclosureHeaderRule builds a rule for a header whose mere presence
// leaks server/framework details (X-Powered-By, X-AspNet-Version,
// X-AspNetMvc-Version). Any value is reported as Info ".disclosed";
// absence is OK ".absent". `what` names the leaked information for the
// finding message.
func disclosureHeaderRule(code, header, what string) sdk.CheckRule {
return HeaderRule(HeaderRuleSpec{
Code: code,
Description: "Reports the presence of the " + header + " header, which discloses " + what + " and should be removed.",
Header: header,
Inspect: func(value string, _ HTTPProbe, _ sdk.CheckerOptions) []HeaderResult {
return []HeaderResult{{
Status: sdk.StatusInfo,
Suffix: "disclosed",
Message: fmt.Sprintf("%s exposes %s (%s); attackers can use it to target known vulnerabilities. Remove the header.", header, what, value),
Meta: map[string]any{"fix": "Remove the `" + header + "` header from your responses."},
}}
},
OnMissing: func(_ HTTPProbe, _ sdk.CheckerOptions) []HeaderResult {
return []HeaderResult{{
Status: sdk.StatusOK,
Suffix: "absent",
Message: header + " is not set, which is correct (it would disclose " + what + ").",
}}
},
})
}
// inspectServerHeader flags Server values that disclose product/version
// detail (e.g. "nginx/1.18.0"). A bare, non-informative token
// ("Server: webserver") is accepted: OWASP recommends either removing the
// header or setting a non-informative value, so the latter is not a
// finding.
func inspectServerHeader(value string, _ HTTPProbe, _ sdk.CheckerOptions) []HeaderResult {
if serverHeaderIsInformative(value) {
return []HeaderResult{{
Status: sdk.StatusInfo,
Suffix: "disclosed",
Message: "Server header discloses the origin server software (" + value + "). Remove it or set a non-informative value such as `Server: webserver`.",
Meta: map[string]any{"fix": "Remove the `Server` header or replace its value with a non-informative one (e.g. `Server: webserver`)."},
}}
}
return []HeaderResult{{
Status: sdk.StatusOK,
Suffix: "ok",
Message: "Server header is set to a non-informative value (" + value + ").",
}}
}
// serverHeaderIsInformative reports whether a Server value reveals more
// than a generic product name. A version number (digits) or a
// product/version separator ("/") is the usual giveaway.
func serverHeaderIsInformative(value string) bool {
return strings.ContainsAny(value, "0123456789") || strings.Contains(value, "/")
}
// HSTS ----------------------------------------------------------------