diff --git a/scan/best_practices.go b/scan/best_practices.go index 85196bf..164b3c9 100644 --- a/scan/best_practices.go +++ b/scan/best_practices.go @@ -12,6 +12,10 @@ func (s *Scan) WithHTTPTraceMethodBestPracticesScan() *Scan { return s.AddScanHandler(bestpractices.HTTPTraceMethodScanHandler) } +func (s *Scan) WithServerSignatureScan() *Scan { + return s.AddScanHandler(bestpractices.ServerSignatureScanHandler) +} + func (s *Scan) WithAllBestPracticesScans() *Scan { - return s.WithHTTPHeadersBestPracticesScan().WithHTTPTraceMethodBestPracticesScan() + return s.WithHTTPHeadersBestPracticesScan().WithHTTPTraceMethodBestPracticesScan().WithServerSignatureScan() } diff --git a/scan/best_practices/server_signature.go b/scan/best_practices/server_signature.go new file mode 100644 index 0000000..dbc2505 --- /dev/null +++ b/scan/best_practices/server_signature.go @@ -0,0 +1,53 @@ +package bestpractices + +import ( + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/internal/request" + "github.com/cerberauth/vulnapi/internal/scan" + "github.com/cerberauth/vulnapi/report" +) + +const ( + ServerSignatureSeverityLevel = 1 + ServerSignatureVulnerabilityName = "Server Signature Exposed" + ServerSignatureVulnerabilityDescription = "A Server signature is exposed in an header." +) + +var SignatureHeaders = []string{"Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version"} + +func CheckSignatureHeader(operation *request.Operation, headers map[string][]string, r *report.ScanReport) bool { + for _, header := range SignatureHeaders { + value := headers[header] + if len(value) > 0 { + r.AddVulnerabilityReport(&report.VulnerabilityReport{ + SeverityLevel: ServerSignatureSeverityLevel, + Name: ServerSignatureVulnerabilityName, + Description: ServerSignatureVulnerabilityDescription, + Operation: operation, + }) + + return false + } + } + + return true +} + +func ServerSignatureScanHandler(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) { + r := report.NewScanReport() + + ss.SetAttackValue(ss.GetValidValue()) + vsa, err := scan.ScanURL(operation, &ss) + r.AddScanAttempt(vsa).End() + if err != nil { + return r, err + } + + if vsa.Err != nil { + return r, vsa.Err + } + + CheckSignatureHeader(operation, vsa.Response.Header, r) + + return r, nil +} diff --git a/scan/best_practices/server_signature_test.go b/scan/best_practices/server_signature_test.go new file mode 100644 index 0000000..15648b7 --- /dev/null +++ b/scan/best_practices/server_signature_test.go @@ -0,0 +1,55 @@ +package bestpractices_test + +import ( + "net/http" + "testing" + + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/internal/request" + "github.com/cerberauth/vulnapi/report" + bestpractices "github.com/cerberauth/vulnapi/scan/best_practices" + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCheckSignatureHeaderWithSignatureHeader(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + token := "token" + securityScheme := auth.NewAuthorizationBearerSecurityScheme("default", &token) + operation := request.NewOperation("http://localhost:8080/", "GET", nil, nil, nil) + vulnerabilityReport := report.VulnerabilityReport{ + SeverityLevel: bestpractices.ServerSignatureSeverityLevel, + Name: bestpractices.ServerSignatureVulnerabilityName, + Description: bestpractices.ServerSignatureVulnerabilityDescription, + Operation: operation, + } + + httpmock.RegisterResponder(operation.Method, operation.Url, httpmock.NewBytesResponder(204, nil).HeaderAdd(http.Header{"Server": []string{"Apache/2.4.29 (Ubuntu)"}})) + + report, err := bestpractices.ServerSignatureScanHandler(operation, securityScheme) + + require.NoError(t, err) + assert.Equal(t, 1, httpmock.GetTotalCallCount()) + assert.True(t, report.HasVulnerabilityReport()) + assert.Equal(t, report.GetVulnerabilityReports()[0], &vulnerabilityReport) +} + +func TestCheckSignatureHeaderWithoutSignatureHeader(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + token := "token" + securityScheme := auth.NewAuthorizationBearerSecurityScheme("default", &token) + operation := request.NewOperation("http://localhost:8080/", "GET", nil, nil, nil) + + httpmock.RegisterResponder(operation.Method, operation.Url, httpmock.NewBytesResponder(204, nil)) + + report, err := bestpractices.ServerSignatureScanHandler(operation, securityScheme) + + require.NoError(t, err) + assert.Equal(t, 1, httpmock.GetTotalCallCount()) + assert.False(t, report.HasVulnerabilityReport()) +}