Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add http headers best practices scan #42

Merged
merged 1 commit into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions cmd/scan/curl.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,19 @@ func NewCURLScanCmd() (scanCmd *cobra.Command) {
log.Fatal(err)
}

rpr, _, err := scan.WithAllVulnsScans().Execute()
rpr, _, err := scan.WithAllVulnsScans().WithAllBestPracticesScans().Execute()
if err != nil {
log.Fatal(err)
}

if !rpr.HasVulnerability() {
log.Println("Congratulations! No vulnerability has been discovered!")
for _, r := range rpr.GetVulnerabilityReports() {
log.Println(r)
}

for _, r := range rpr.GetVulnerabilityReports() {
log.Fatalln(r)
if !rpr.HasVulnerability() {
log.Println("Congratulations! No vulnerability has been discovered!")
} else {
log.Fatalln("There is one or more vulnerabilies you should know.")
}
},
}
Expand Down
12 changes: 7 additions & 5 deletions cmd/scan/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,19 @@ func NewOpenAPIScanCmd() (scanCmd *cobra.Command) {
log.Fatal(err)
}

rpr, _, err := scan.WithAllVulnsScans().Execute()
rpr, _, err := scan.WithAllVulnsScans().WithAllBestPracticesScans().Execute()
if err != nil {
log.Fatal(err)
}

if !rpr.HasVulnerability() {
log.Println("Congratulations! No vulnerability has been discovered!")
for _, r := range rpr.GetVulnerabilityReports() {
log.Println(r)
}

for _, r := range rpr.GetVulnerabilityReports() {
log.Fatalln(r)
if !rpr.HasVulnerability() {
log.Println("Congratulations! No vulnerability has been discovered!")
} else {
log.Fatalln("There is one or more vulnerabilies you should know.")
}
},
}
Expand Down
13 changes: 13 additions & 0 deletions scan/best_practices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package scan

import (
bestpractices "github.com/cerberauth/vulnapi/scan/best_practices"
)

func (s *Scan) WithHTTPHeadersBestPracticesScan() *Scan {
return s.AddScanHandler(bestpractices.HTTPHeadersBestPracticesScanHandler)
}

func (s *Scan) WithAllBestPracticesScans() *Scan {
return s.WithHTTPHeadersBestPracticesScan()
}
151 changes: 151 additions & 0 deletions scan/best_practices/http_headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package bestpractices

import (
"net/http"
"strings"

"github.com/cerberauth/vulnapi/internal/auth"
restapi "github.com/cerberauth/vulnapi/internal/rest_api"
"github.com/cerberauth/vulnapi/report"
)

const (
CSPHTTPHeader = "Content-Security-Policy"
HSTSHTTPHeader = "Strict-Transport-Security"
CORSOriginHTTPHeader = "Access-Control-Allow-Origin"
XContentTypeOptionsHTTPHeader = "X-Content-Type-Options"
XFrameOptionsHTTPHeader = "X-Frame-Options"
)

const (
CSPHTTPHeaderSeverityLevel = 1
CSPHTTPHeaderIsNotSetVulnerabilityName = "CSP Header is not set"
CSPHTTPHeaderIsNotSetVulnerabilityDescription = "No Content Security Policy (CSP) Header has been detected in HTTP Response."
CSPHTTPHeaderFrameAncestorsIsNotSetVulnerabilityName = "CSP frame-ancestors policy is not set"
CSPHTTPHeaderFrameAncestorsIsNotSetVulnerabilityDescription = "No frame-ancestors policy has been set in CSP HTTP Response Header."

HSTSHTTPHeaderSeverityLevel = 1
HSTSHTTPHeaderIsNotSetVulnerabilityName = "HSTS Header is not set"
HSTSHTTPHeaderIsNotSetVulnerabilityDescription = "No HSTS Header has been detected in HTTP Response."

CORSHTTPHeaderSeverityLevel = 1
CORSHTTPHeaderIsNotSetVulnerabilityName = "CORS Header is not set"
CORSHTTPHeaderIsNotSetVulnerabilityDescription = "No CORS Header has been detected in HTTP Response."
CORSHTTPHeaderIsPermisiveVulnerabilityName = "CORS Header is set but permissive"
CORSHTTPHeaderIsPermisiveVulnerabilityDescription = "CORS Header has been detected in HTTP Response but is permissive."

XContentTypeOptionsHTTPHeaderIsNotSetSeverityLevel = 1
XContentTypeOptionsHTTPHeaderIsNotSetVulnerabilityName = "X-Content-Type-Options Header is not set"
XContentTypeOptionsHTTPHeaderIsNotSetVulnerabilityDescription = "No X-Content-Type-Options Header has been detected in HTTP Response."

XFrameOptionsHTTPHeaderIsNotSetSeverityLevel = 1
XFrameOptionsHTTPHeaderIsNotSetVulnerabilityName = "X-Frame-Options Header is not set"
XFrameOptionsHTTPHeaderIsNotSetVulnerabilityDescription = "No X-Frame-Options Header has been detected in HTTP Response."
)

func checkCSPHeader(o *auth.Operation, headers http.Header, r *report.ScanReport) bool {
cspHeader := headers.Get(CSPHTTPHeader)
if cspHeader == "" {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: CSPHTTPHeaderSeverityLevel,
Name: CSPHTTPHeaderIsNotSetVulnerabilityName,
Description: CSPHTTPHeaderIsNotSetVulnerabilityDescription,
Url: o.Url,
})

return false
}

directives := strings.Split(cspHeader, ";")
for _, directive := range directives {
directive = strings.TrimSpace(directive)
if strings.HasPrefix(directive, "frame-ancestors") {
// Check if frame-ancestors directive is not equal to "none"
if strings.Contains(directive, "none") {
return true
}
}
}

r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: CSPHTTPHeaderSeverityLevel,
Name: CSPHTTPHeaderFrameAncestorsIsNotSetVulnerabilityName,
Description: CSPHTTPHeaderFrameAncestorsIsNotSetVulnerabilityDescription,
Url: o.Url,
})

return false
}

func CheckCORSAllowOrigin(o *auth.Operation, headers http.Header, r *report.ScanReport) bool {
allowOrigin := headers.Get(CORSOriginHTTPHeader)
if allowOrigin == "" {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: CORSHTTPHeaderSeverityLevel,
Name: CORSHTTPHeaderIsNotSetVulnerabilityName,
Description: CORSHTTPHeaderIsNotSetVulnerabilityDescription,
Url: o.Url,
})

return false
}

// Check if the Access-Control-Allow-Origin header is not "*" (wildcard)
if allowOrigin != "*" {
return true
}

r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: CORSHTTPHeaderSeverityLevel,
Name: CORSHTTPHeaderIsPermisiveVulnerabilityName,
Description: CORSHTTPHeaderIsPermisiveVulnerabilityDescription,
Url: o.Url,
})

return false
}

func HTTPHeadersBestPracticesScanHandler(o *auth.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) {
r := report.NewScanReport()
token := ss.GetValidValue().(string)

ss.SetAttackValue(token)
vsa := restapi.ScanRestAPI(o, ss)
r.AddScanAttempt(vsa).End()

if vsa.Err != nil {
return r, vsa.Err
}

checkCSPHeader(o, vsa.Response.Header, r)
CheckCORSAllowOrigin(o, vsa.Response.Header, r)

if hstsHeader := vsa.Response.Header.Get(HSTSHTTPHeader); hstsHeader == "" {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: HSTSHTTPHeaderSeverityLevel,
Name: HSTSHTTPHeaderIsNotSetVulnerabilityName,
Description: HSTSHTTPHeaderIsNotSetVulnerabilityDescription,
Url: o.Url,
})
}

if xContentTypeOptionsHeader := vsa.Response.Header.Get(XContentTypeOptionsHTTPHeader); xContentTypeOptionsHeader == "" {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: XContentTypeOptionsHTTPHeaderIsNotSetSeverityLevel,
Name: XContentTypeOptionsHTTPHeaderIsNotSetVulnerabilityName,
Description: XContentTypeOptionsHTTPHeaderIsNotSetVulnerabilityDescription,
Url: o.Url,
})
}

if xFrameOptionsHeader := vsa.Response.Header.Get(XFrameOptionsHTTPHeader); xFrameOptionsHeader == "" {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: XFrameOptionsHTTPHeaderIsNotSetSeverityLevel,
Name: XFrameOptionsHTTPHeaderIsNotSetVulnerabilityName,
Description: XFrameOptionsHTTPHeaderIsNotSetVulnerabilityDescription,
Url: o.Url,
})
}

return r, nil
}
Loading
Loading