From c7408c92622684612f1e82c75a60bd980c47da6b Mon Sep 17 00:00:00 2001 From: Emmanuel Gautier Date: Tue, 22 Oct 2024 00:26:30 +0200 Subject: [PATCH] feat: add http method override scan --- .github/workflows/scans.yml | 2 + docs/vulnerabilities.mdx | 3 +- .../broken-authentication/jwt-alg-none.mdx | 2 +- .../jwt-blank-secret.mdx | 2 +- .../jwt-null-signature.mdx | 2 +- .../broken-authentication/jwt-weak-secret.mdx | 2 +- .../security-misconfiguration/_meta.json | 3 + .../graphql-introspection.md | 2 +- .../http-method-allow-override.md | 76 ++++++++ internal/auth/jwt_bearer.go | 8 + internal/auth/jwt_bearer_test.go | 37 ++++ internal/request/operation.go | 8 + internal/request/operation_test.go | 25 +++ report/cwe.go | 1 + .../jwt/alg_none/alg_none.go | 12 +- .../http_method_override.go | 162 ++++++++++++++++++ .../http_method_override_test.go | 93 ++++++++++ 17 files changed, 428 insertions(+), 12 deletions(-) create mode 100644 docs/vulnerabilities/security-misconfiguration/http-method-allow-override.md create mode 100644 scan/misconfiguration/http_method_override/http_method_override.go create mode 100644 scan/misconfiguration/http_method_override/http_method_override_test.go diff --git a/.github/workflows/scans.yml b/.github/workflows/scans.yml index 03963988..26f2d632 100644 --- a/.github/workflows/scans.yml +++ b/.github/workflows/scans.yml @@ -97,6 +97,8 @@ jobs: url: "http://localhost:8080/cookies/samesite-none" - challenge: "misconfiguration.http_cookies" url: "http://localhost:8080/cookies/no-expiration" + - challenge: "misconfiguration.http_method_override" + url: "http://localhost:8080/cookies/http-method-override" steps: - uses: actions/checkout@v4 diff --git a/docs/vulnerabilities.mdx b/docs/vulnerabilities.mdx index 60ee65d1..1aeecf39 100644 --- a/docs/vulnerabilities.mdx +++ b/docs/vulnerabilities.mdx @@ -16,7 +16,7 @@ | JWT Expired | API2:2023 Broken Authentication | High | | | Discoverable OpenAPI | API7:2023 Server Side Request Forgery | Info | ✅ | | Discoverable GraphQL Endpoint | API7:2023 Server Side Request Forgery | Info | ✅ | -| [GraphQL Introspection Enabled](./vulnerabilities/security-misconfiguration/graphql-introspection.md) | API7:2023 Server Side Request Forgery | Info | ✅ | +| [GraphQL Introspection Enabled](./vulnerabilities/security-misconfiguration/graphql-introspection.md) | API8:2023 Security Misconfiguration | Info | ✅ | | Secrets Leak | API8:2023 Security Misconfiguration | High | | | Directory Listing | API8:2023 Security Misconfiguration | Medium | | | Private IP Disclosure | API8:2023 Security Misconfiguration | Low | | @@ -26,6 +26,7 @@ | No Cookie expiration | API8:2023 Security Misconfiguration | Info | ✅ | | No CORS Headers | API8:2023 Security Misconfiguration | Info | ✅ | | Permissive CORS Headers | API8:2023 Security Misconfiguration | Info | ✅ | +| HTTP Method Override Enabled | API8:2023 Security Misconfiguration | Info - High | ✅ | | X-Content-Type-Options Header Not Set | API8:2023 Security Misconfiguration | Info | ✅ | | X-Frame-Options Header Not Set | API8:2023 Security Misconfiguration | Info | ✅ | | CSP Header Not Set | API8:2023 Security Misconfiguration | Info | ✅ | diff --git a/docs/vulnerabilities/broken-authentication/jwt-alg-none.mdx b/docs/vulnerabilities/broken-authentication/jwt-alg-none.mdx index eb887865..dd2be528 100644 --- a/docs/vulnerabilities/broken-authentication/jwt-alg-none.mdx +++ b/docs/vulnerabilities/broken-authentication/jwt-alg-none.mdx @@ -96,7 +96,7 @@ echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan openapi [OpenAPI_Path_Or_URL] --sca ```bash copy -echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan graphql -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.alg_none [url] +vulnapi scan graphql -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.alg_none [url] ``` diff --git a/docs/vulnerabilities/broken-authentication/jwt-blank-secret.mdx b/docs/vulnerabilities/broken-authentication/jwt-blank-secret.mdx index 0f657587..7867a740 100644 --- a/docs/vulnerabilities/broken-authentication/jwt-blank-secret.mdx +++ b/docs/vulnerabilities/broken-authentication/jwt-blank-secret.mdx @@ -87,7 +87,7 @@ echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan openapi [OpenAPI_Path_Or_URL] --sca ```bash copy -echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan graphql -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.blank_secret [url] +vulnapi scan graphql -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.blank_secret [url] ``` diff --git a/docs/vulnerabilities/broken-authentication/jwt-null-signature.mdx b/docs/vulnerabilities/broken-authentication/jwt-null-signature.mdx index a2ed9220..399e392b 100644 --- a/docs/vulnerabilities/broken-authentication/jwt-null-signature.mdx +++ b/docs/vulnerabilities/broken-authentication/jwt-null-signature.mdx @@ -88,7 +88,7 @@ echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan openapi [OpenAPI_Path_Or_URL] --sca ```bash copy -echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan graphql -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.null_signature [url] +vulnapi scan graphql -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.null_signature [url] ``` diff --git a/docs/vulnerabilities/broken-authentication/jwt-weak-secret.mdx b/docs/vulnerabilities/broken-authentication/jwt-weak-secret.mdx index 391df3ab..1a9f7401 100644 --- a/docs/vulnerabilities/broken-authentication/jwt-weak-secret.mdx +++ b/docs/vulnerabilities/broken-authentication/jwt-weak-secret.mdx @@ -95,7 +95,7 @@ echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan openapi [OpenAPI_Path_Or_URL] --sca ```bash copy -echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan graphql -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.weak_secret [url] +vulnapi scan graphql -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.weak_secret [url] ``` diff --git a/docs/vulnerabilities/security-misconfiguration/_meta.json b/docs/vulnerabilities/security-misconfiguration/_meta.json index d219d6c1..314e577b 100644 --- a/docs/vulnerabilities/security-misconfiguration/_meta.json +++ b/docs/vulnerabilities/security-misconfiguration/_meta.json @@ -1,5 +1,8 @@ { "graphql-introspection": { "title": "GraphQL Introspection Enabled" + }, + "http-method-allow-override": { + "title": "HTTP Method Override Enabled" } } \ No newline at end of file diff --git a/docs/vulnerabilities/security-misconfiguration/graphql-introspection.md b/docs/vulnerabilities/security-misconfiguration/graphql-introspection.md index 1ddbded7..7b8d0ccf 100644 --- a/docs/vulnerabilities/security-misconfiguration/graphql-introspection.md +++ b/docs/vulnerabilities/security-misconfiguration/graphql-introspection.md @@ -24,7 +24,7 @@ description: GraphQL introspection is a feature that allows clients to query the OWASP Category - OWASP API8:2023 Security Misconfiguration + OWASP API8:2023 Security Misconfiguration diff --git a/docs/vulnerabilities/security-misconfiguration/http-method-allow-override.md b/docs/vulnerabilities/security-misconfiguration/http-method-allow-override.md new file mode 100644 index 00000000..d48a46e0 --- /dev/null +++ b/docs/vulnerabilities/security-misconfiguration/http-method-allow-override.md @@ -0,0 +1,76 @@ +--- +description: HTTP Method Override is a feature that allows clients to use HTTP methods other than GET and POST to perform actions on the server. It can be used to override the default behavior of the server and execute custom actions, but it can also be used by attackers to bypass security controls and perform unauthorized actions. +--- + +# HTTP Method Override Enabled + + + + + + + + + + + + + + + + + + +
SeverityLow - High
CVEs + +
Classifications + +
OWASP Category + OWASP API8:2023 Security Misconfiguration +
+ +HTTP Method Override is a feature that allows clients to use HTTP methods other than GET and POST to perform actions on the server. It can be used to override the default behavior of the server and execute custom actions, but it can also be used by attackers to bypass security controls and perform unauthorized actions. + +## What is the impact? + +Attackers can exploit this feature to bypass security controls and perform unauthorized actions on the server. Some of the common attacks that can be performed using HTTP Method Override include: +- CSRF attacks +- Bypassing authentication +- Bypassing access controls + +## How to test? + +If you want to test only the "HTTP Method Allow Override Enabled" issues, you can use the following command: + + + +```bash copy +echo "vulnapi scan curl [url] --scans misconfiguration.http_method_override +``` + + +```bash copy +vulnapi scan openapi [OpenAPI_Path_Or_URL] --scans misconfiguration.http_method_override +``` + + +```bash copy +vulnapi scan graphql --scans misconfiguration.http_method_override [url] +``` + + + +## How to remediate? + +To remediate this issue, you should disable the HTTP Method Override feature on the server. You can do this by configuring the server to only accept the standard HTTP methods (GET, POST, PUT, DELETE, etc.) and reject any other methods that are not explicitly allowed. + +## References + +- [X-HTTP-Method](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-odata/bdbabfa6-8c4a-4741-85a9-8d93ffd66c41) +- [X-HTTP-Method-Override] diff --git a/internal/auth/jwt_bearer.go b/internal/auth/jwt_bearer.go index d28c4f80..82fe6c28 100644 --- a/internal/auth/jwt_bearer.go +++ b/internal/auth/jwt_bearer.go @@ -41,6 +41,14 @@ func NewAuthorizationJWTBearerSecurityScheme(name string, value *string) (*JWTBe }, nil } +func MustNewAuthorizationJWTBearerSecurityScheme(name string, value *string) *JWTBearerSecurityScheme { + scheme, err := NewAuthorizationJWTBearerSecurityScheme(name, value) + if err != nil { + panic(err) + } + return scheme +} + func (ss *JWTBearerSecurityScheme) GetType() Type { return ss.Type } diff --git a/internal/auth/jwt_bearer_test.go b/internal/auth/jwt_bearer_test.go index c3a4ab9c..12be6610 100644 --- a/internal/auth/jwt_bearer_test.go +++ b/internal/auth/jwt_bearer_test.go @@ -31,6 +31,43 @@ func TestNewAuthorizationJWTBearerSecuritySchemeWithInvalidJWT(t *testing.T) { assert.Error(t, err) } +func TestMustNewAuthorizationJWTBearerSecurityScheme(t *testing.T) { + t.Run("ValidJWT", func(t *testing.T) { + name := "token" + value := jwt.FakeJWT + ss := auth.MustNewAuthorizationJWTBearerSecurityScheme(name, &value) + + assert.NotNil(t, ss) + assert.Equal(t, auth.HttpType, ss.Type) + assert.Equal(t, auth.BearerScheme, ss.Scheme) + assert.Equal(t, auth.InHeader, ss.In) + assert.Equal(t, name, ss.Name) + assert.Equal(t, &value, ss.ValidValue) + assert.Equal(t, "", ss.AttackValue) + }) + + t.Run("InvalidJWT", func(t *testing.T) { + name := "token" + value := "abc123" + assert.Panics(t, func() { + auth.MustNewAuthorizationJWTBearerSecurityScheme(name, &value) + }) + }) + + t.Run("NilValue", func(t *testing.T) { + name := "token" + ss := auth.MustNewAuthorizationJWTBearerSecurityScheme(name, nil) + + assert.NotNil(t, ss) + assert.Equal(t, auth.HttpType, ss.Type) + assert.Equal(t, auth.BearerScheme, ss.Scheme) + assert.Equal(t, auth.InHeader, ss.In) + assert.Equal(t, name, ss.Name) + assert.Nil(t, ss.ValidValue) + assert.Equal(t, "", ss.AttackValue) + }) +} + func TestAuthorizationJWTBearerSecurityScheme_GetScheme(t *testing.T) { name := "token" value := jwt.FakeJWT diff --git a/internal/request/operation.go b/internal/request/operation.go index d222708c..f1717359 100644 --- a/internal/request/operation.go +++ b/internal/request/operation.go @@ -71,6 +71,14 @@ func NewOperation(method string, operationUrl string, body *bytes.Buffer, client }, nil } +func MustNewOperation(method string, operationUrl string, body *bytes.Buffer, client *Client) *Operation { + operation, err := NewOperation(method, operationUrl, body, client) + if err != nil { + panic(err) + } + return operation +} + func (operation *Operation) IsReachable() error { host := operation.URL.Host if _, _, err := net.SplitHostPort(host); err != nil { diff --git a/internal/request/operation_test.go b/internal/request/operation_test.go index 62cffbf6..f2a1371a 100644 --- a/internal/request/operation_test.go +++ b/internal/request/operation_test.go @@ -26,6 +26,31 @@ func TestNewOperation(t *testing.T) { assert.Equal(t, body, operation.Body) } +func TestMustNewOperation(t *testing.T) { + t.Run("ValidOperation", func(t *testing.T) { + url := "http://example.com" + method := http.MethodGet + body := bytes.NewBufferString("test") + + operation := request.MustNewOperation(method, url, body, nil) + + assert.NotNil(t, operation) + assert.Equal(t, url, operation.URL.String()) + assert.Equal(t, method, operation.Method) + assert.Equal(t, body, operation.Body) + }) + + t.Run("InvalidURL", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic for invalid URL, but did not panic") + } + }() + + request.MustNewOperation(http.MethodGet, ":", nil, nil) + }) +} + func TestOperation_IsReachable(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) diff --git a/report/cwe.go b/report/cwe.go index cab838b5..8c6cf9f3 100644 --- a/report/cwe.go +++ b/report/cwe.go @@ -5,6 +5,7 @@ type CWE string const ( CWE_16_Configuration CWE = "CWE-16: Configuration" + CWE_287_Improper_Authentication CWE = "CWE-287: Improper Authentication" CWE_345_Insufficient_Verification_Authenticity CWE = "CWE-345: Insufficient Verification of Data Authenticity" CWE_489_Active_Debug_Code CWE = "CWE-489: Active Debug Code" CWE_613_Insufficient_Session_Expiration CWE = "CWE-613: Insufficient Session Expiration" diff --git a/scan/broken_authentication/jwt/alg_none/alg_none.go b/scan/broken_authentication/jwt/alg_none/alg_none.go index a8e8e03d..8cb60042 100644 --- a/scan/broken_authentication/jwt/alg_none/alg_none.go +++ b/scan/broken_authentication/jwt/alg_none/alg_none.go @@ -57,12 +57,12 @@ var algs = []string{ } func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { - vulnReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) + issueReport := report.NewIssueReport(issue).WithOperation(operation).WithSecurityScheme(securityScheme) r := report.NewScanReport(AlgNoneJwtScanID, AlgNoneJwtScanName, operation) if !ShouldBeScanned(securityScheme) { - vulnReport.Skip() - r.AddIssueReport(vulnReport).End() + issueReport.Skip() + r.AddIssueReport(issueReport).End() return r, nil } @@ -86,16 +86,16 @@ func ScanHandler(operation *request.Operation, securityScheme auth.SecuritySchem return r, err } r.AddScanAttempt(vsa) - vulnReport.WithBooleanStatus(scan.IsUnauthorizedStatusCodeOrSimilar(vsa.Response)) + issueReport.WithBooleanStatus(scan.IsUnauthorizedStatusCodeOrSimilar(vsa.Response)) - if vulnReport.HasFailed() { + if issueReport.HasFailed() { r.WithData(&AlgNoneData{Alg: strings.Clone(alg)}) break } } r.End() - r.AddIssueReport(vulnReport) + r.AddIssueReport(issueReport) return r, nil } diff --git a/scan/misconfiguration/http_method_override/http_method_override.go b/scan/misconfiguration/http_method_override/http_method_override.go new file mode 100644 index 00000000..0852098b --- /dev/null +++ b/scan/misconfiguration/http_method_override/http_method_override.go @@ -0,0 +1,162 @@ +package httpmethodoverride + +import ( + "net/http" + + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/internal/request" + "github.com/cerberauth/vulnapi/internal/scan" + "github.com/cerberauth/vulnapi/report" +) + +const ( + HTTPMethodOverrideScanID = "misconfiguration.http_method_override" + HTTPMethodOverrideScanName = "HTTP Method Override Misconfiguration" +) + +var httpMethodOverrideIssue = report.Issue{ + ID: "security_misconfiguration.http_method_allow_override", + Name: "Possible HTTP Method Override detected", + URL: "https://vulnapi.cerberauth.com/docs/vulnerabilities/security-misconfiguration/http-method-allow-override?utm_source=vulnapi", + + Classifications: &report.Classifications{ + OWASP: report.OWASP_2023_SecurityMisconfiguration, + CWE: report.CWE_16_Configuration, + }, + + CVSS: report.CVSS{ + Version: 4.0, + Vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N", + Score: 0, + }, +} + +var httpMethodOverrideAuthenticationByPassIssue = report.Issue{ + ID: "security_misconfiguration.http_method_override_authentication_bypass", + Name: "Possible HTTP Method Override with authentication bypass detected", + URL: "https://vulnapi.cerberauth.com/docs/vulnerabilities/security-misconfiguration/http-method-allow-override?utm_source=vulnapi", + + Classifications: &report.Classifications{ + OWASP: report.OWASP_2023_SecurityMisconfiguration, + CWE: report.CWE_287_Improper_Authentication, + }, + + CVSS: report.CVSS{ + Version: 4.0, + Vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N", + Score: 8.8, + }, +} + +var httpMethods = []string{ + http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, +} + +var methodOverrideHeaders = []string{ + "X-HTTP-Method-Override", + "X-Http-Method-Override", + "X-HTTP-Method", + "X-Http-Method", + "X-Method-Override", +} + +var methodOverrideQueryParams = []string{ + "_method", + "method", + "httpMethod", + "_httpMethod", +} + +func ScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) { + var err error + var newOperation *request.Operation + + httpMethodOverrideIssueReport := report.NewIssueReport(httpMethodOverrideIssue).WithOperation(operation).WithSecurityScheme(securityScheme) + httpMethodOverrideAuthenticationByPassIssueReport := report.NewIssueReport(httpMethodOverrideAuthenticationByPassIssue) + r := report.NewScanReport(HTTPMethodOverrideScanID, HTTPMethodOverrideScanName, operation) + + newOperation = operation.Clone() + initialAttempt, err := scan.ScanURL(newOperation, &securityScheme) + if err != nil { + return r, err + } + r.AddScanAttempt(initialAttempt) + + if initialAttempt.Response.StatusCode == http.StatusMethodNotAllowed { + r.AddIssueReport(httpMethodOverrideIssueReport.Skip()).End() + return r, nil + } + + var methodAttempt *scan.IssueScanAttempt + for _, method := range httpMethods { + if method == operation.Method { + continue + } + + newOperation = operation.Clone() + newOperation.Method = method + methodAttempt, err = scan.ScanURL(newOperation, &securityScheme) + r.AddScanAttempt(methodAttempt) + if err == nil && methodAttempt.Response.StatusCode == http.StatusMethodNotAllowed { + break + } + } + + if err != nil { + return r, err + } + + var attemptFailed = false + var attempt *scan.IssueScanAttempt + newOperationMethod := methodAttempt.Request.Method + for _, header := range methodOverrideHeaders { + newOperation = operation.Clone() + newOperation.Header.Set(header, operation.Method) + newOperation.Method = newOperationMethod + attempt, err = scan.ScanURL(newOperation, &securityScheme) + r.AddScanAttempt(attempt) + if err == nil && attempt.Response.StatusCode == initialAttempt.Response.StatusCode { + attemptFailed = true + break + } + } + + if attemptFailed { + for _, queryParam := range methodOverrideQueryParams { + newOperation = operation.Clone() + newOperation.URL.Query().Set(queryParam, operation.Method) + newOperation.Method = newOperationMethod + attempt, err = scan.ScanURL(newOperation, &securityScheme) + r.AddScanAttempt(attempt) + if err == nil && attempt.Response.StatusCode == initialAttempt.Response.StatusCode { + attemptFailed = true + break + } + } + } + + if !attemptFailed { + r.AddIssueReport(httpMethodOverrideIssueReport.Pass()).AddIssueReport(httpMethodOverrideAuthenticationByPassIssueReport.Skip()).End() + return r, nil + } + + r.AddIssueReport(httpMethodOverrideIssueReport.Fail()) + if _, ok := securityScheme.(*auth.NoAuthSecurityScheme); ok { + return r.AddIssueReport(httpMethodOverrideAuthenticationByPassIssueReport.Skip()).End(), nil + } + + noAuthSecurityScheme := auth.SecurityScheme(auth.NewNoAuthSecurityScheme()) + attempt, err = scan.ScanURL(newOperation, &noAuthSecurityScheme) + if err != nil { + return r, err + } + httpMethodOverrideAuthenticationByPassIssueReport.WithBooleanStatus(scan.IsUnauthorizedStatusCodeOrSimilar(attempt.Response)) + r.AddIssueReport(httpMethodOverrideAuthenticationByPassIssueReport).AddScanAttempt(attempt).End() + + return r, nil +} diff --git a/scan/misconfiguration/http_method_override/http_method_override_test.go b/scan/misconfiguration/http_method_override/http_method_override_test.go new file mode 100644 index 00000000..cc66d5cb --- /dev/null +++ b/scan/misconfiguration/http_method_override/http_method_override_test.go @@ -0,0 +1,93 @@ +package httpmethodoverride_test + +import ( + "net/http" + "testing" + + "github.com/cerberauth/vulnapi/internal/auth" + "github.com/cerberauth/vulnapi/internal/request" + "github.com/cerberauth/vulnapi/jwt" + httpmethodoverride "github.com/cerberauth/vulnapi/scan/misconfiguration/http_method_override" + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHTTPMethodOverrideScanHandler(t *testing.T) { + value := jwt.FakeJWT + tests := []struct { + name string + operation *request.Operation + securityScheme auth.SecurityScheme + }{ + { + name: "MethodNotAllowed", + operation: request.MustNewOperation(http.MethodGet, "http://example.com", nil, nil), + securityScheme: auth.NewNoAuthSecurityScheme(), + }, + { + name: "MethodOverrideDetected", + operation: request.MustNewOperation(http.MethodPost, "http://example.com/test", nil, nil), + securityScheme: auth.NewNoAuthSecurityScheme(), + }, + { + name: "AuthenticationBypassDetected", + operation: request.MustNewOperation(http.MethodPost, "http://example.com/test", nil, nil), + securityScheme: auth.MustNewAuthorizationJWTBearerSecurityScheme("securityScheme", &value), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := httpmethodoverride.ScanHandler(tt.operation, tt.securityScheme) + if err != nil { + t.Errorf("ScanHandler() error = %v", err) + return + } + if got == nil { + t.Errorf("ScanHandler() got = nil, want non-nil") + } + }) + } +} + +func TestHTTPMethodOverrideScanHandler_Passed(t *testing.T) { + client := request.DefaultClient + httpmock.ActivateNonDefault(client.Client) + defer httpmock.DeactivateAndReset() + + securityScheme := auth.NewNoAuthSecurityScheme() + operation := request.MustNewOperation(http.MethodGet, "http://localhost:8080/", nil, client) + httpmock.RegisterResponder(operation.Method, operation.URL.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil)) + httpmock.RegisterResponder(http.MethodPost, operation.URL.String(), httpmock.NewBytesResponder(http.StatusMethodNotAllowed, nil)) + httpmock.RegisterResponder(http.MethodPut, operation.URL.String(), httpmock.NewBytesResponder(http.StatusMethodNotAllowed, nil)) + httpmock.RegisterResponder(http.MethodPatch, operation.URL.String(), httpmock.NewBytesResponder(http.StatusMethodNotAllowed, nil)) + httpmock.RegisterResponder(http.MethodDelete, operation.URL.String(), httpmock.NewBytesResponder(http.StatusMethodNotAllowed, nil)) + httpmock.RegisterResponder(http.MethodHead, operation.URL.String(), httpmock.NewBytesResponder(http.StatusMethodNotAllowed, nil)) + + report, err := httpmethodoverride.ScanHandler(operation, securityScheme) + + require.NoError(t, err) + assert.Equal(t, 16, httpmock.GetTotalCallCount()) + assert.Equal(t, 2, len(report.Issues)) + assert.False(t, report.HasFailedIssueReport()) +} + +// func TestHTTPHeadersBestPracticesWithoutCSPScanHandler(t *testing.T) { +// client := request.DefaultClient +// httpmock.ActivateNonDefault(client.Client) +// defer httpmock.DeactivateAndReset() + +// token := "token" +// securityScheme := auth.NewAuthorizationBearerSecurityScheme("default", &token) +// operation, _ := request.NewOperation(http.MethodGet, "http://localhost:8080/", nil, client) +// header := getValidHTTPHeaders(operation) +// header.Del(httpheaders.CSPHTTPHeader) +// httpmock.RegisterResponder(operation.Method, operation.URL.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil).HeaderAdd(header)) + +// report, err := httpheaders.ScanHandler(operation, securityScheme) + +// require.NoError(t, err) +// assert.Equal(t, 1, httpmock.GetTotalCallCount()) +// assert.True(t, report.HasFailedIssueReport()) +// }