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..39c81b87 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.mdx) | 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..5e61d4db 100644
--- a/docs/vulnerabilities/broken-authentication/jwt-alg-none.mdx
+++ b/docs/vulnerabilities/broken-authentication/jwt-alg-none.mdx
@@ -7,35 +7,31 @@ import { Tabs } from 'nextra/components'
# JWT None Algorithm
-
- Severity |
- High |
-
-
- CVEs |
-
-
- |
-
-
- Classifications |
-
-
- |
-
-
- OWASP Category |
-
- OWASP API2:2023 Broken Authentication
- |
-
+
+ Severity |
+ High |
+
+
+ CVEs |
+
+ * [CVE-2015-9235](https://www.cve.org/CVERecord?id=CVE-2015-9235)
+ * [CVE-2015-2951](https://www.cve.org/CVERecord?id=CVE-2015-2951)
+ |
+
+
+ Classifications |
+
+ * [CWE-345: Insufficient Verification of Data Authenticity](https://cwe.mitre.org/data/definitions/345.html)
+ * [CWE-327: Use of a Broken or Risky Cryptographic Algorithm](https://cwe.mitre.org/data/definitions/327.html)
+ * [CWE-20: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html)
+ |
+
+
+ OWASP Category |
+
+ [OWASP API2:2023 Broken Authentication](https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/)
+ |
+
Accepting the "none" algorithm in a JSON Web Token (JWT) occurs when a JWT is signed with the "none" algorithm, it means there is no signature, making it easy for attackers to tamper with the token's content without detection. This can lead to unauthorized access and data manipulation.
@@ -86,7 +82,7 @@ If you want to test only the "JWT Alg None" vulnerability, you can use the follo
```bash copy
-echo "vulnapi scan curl [url] -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.alg_none
+vulnapi scan curl [url] -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.alg_none
```
@@ -96,7 +92,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..a2085f7e 100644
--- a/docs/vulnerabilities/broken-authentication/jwt-blank-secret.mdx
+++ b/docs/vulnerabilities/broken-authentication/jwt-blank-secret.mdx
@@ -7,33 +7,29 @@ import { Tabs } from 'nextra/components'
# JWT Blank Secret
-
- Severity |
- High |
-
-
- CVEs |
-
-
- |
-
-
- Classifications |
-
-
- |
-
-
- OWASP Category |
-
- OWASP API2:2023 Broken Authentication
- |
-
+
+ Severity |
+ High |
+
+
+ CVEs |
+
+ * [CVE-2019-20933](https://www.cve.org/CVERecord?id=CVE-2019-20933)
+ * [CVE-2020-28637](https://www.cve.org/CVERecord?id=CVE-2020-28637)
+ |
+
+
+ Classifications |
+
+ [CWE-287: Improper Authentication](https://cwe.mitre.org/data/definitions/287.html)
+ |
+
+
+ OWASP Category |
+
+ [OWASP API2:2023 Broken Authentication](https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/)
+ |
+
A vulnerability occurs when a JSON Web Token (JWT) is signed with an empty secret. In this scenario, the token lacks proper cryptographic protection, making it susceptible to manipulation. Attackers can modify the token's claims and content without detection, potentially leading to unauthorized access and data tampering.
@@ -77,7 +73,7 @@ If you want to test only the "JWT Blank Secret" vulnerability, you can use the f
```bash copy
-echo "vulnapi scan curl [url] -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.blank_secret
+vulnapi scan curl [url] -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.blank_secret
```
@@ -87,7 +83,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-cross-service-relay-attack.md b/docs/vulnerabilities/broken-authentication/jwt-cross-service-relay-attack.mdx
similarity index 78%
rename from docs/vulnerabilities/broken-authentication/jwt-cross-service-relay-attack.md
rename to docs/vulnerabilities/broken-authentication/jwt-cross-service-relay-attack.mdx
index 01adfd6f..b6da6171 100644
--- a/docs/vulnerabilities/broken-authentication/jwt-cross-service-relay-attack.md
+++ b/docs/vulnerabilities/broken-authentication/jwt-cross-service-relay-attack.mdx
@@ -5,24 +5,24 @@ description: A vulnerability arises when a JSON Web Token (JWT) is signed by the
# JWT Cross Service Relay Attack
-
- Severity |
- High |
-
-
- CVEs |
- |
-
-
- Classifications |
- |
-
-
- OWASP Category |
-
- OWASP API2:2023 Broken Authentication
- |
-
+
+ Severity |
+ High |
+
+
+ CVEs |
+ |
+
+
+ Classifications |
+ |
+
+
+ OWASP Category |
+
+ [OWASP API2:2023 Broken Authentication](https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/)
+ |
+
A vulnerability arises when a JSON Web Token (JWT) is signed by the same service but doesn't verify the issuer (the source of the token) and the audience (the intended recipient). This can lead to security risks, as it means an attacker could create a forged JWT with the same service signature and manipulate the issuer and audience fields. Without proper verification, the service may accept the forged token, potentially granting unauthorized access or compromising the system's security.
diff --git a/docs/vulnerabilities/broken-authentication/jwt-null-signature.mdx b/docs/vulnerabilities/broken-authentication/jwt-null-signature.mdx
index a2ed9220..f5d0da5e 100644
--- a/docs/vulnerabilities/broken-authentication/jwt-null-signature.mdx
+++ b/docs/vulnerabilities/broken-authentication/jwt-null-signature.mdx
@@ -7,32 +7,28 @@ import { Tabs } from 'nextra/components'
# JWT Null Signature
-
- Severity |
- High |
-
-
- CVEs |
-
-
- |
-
-
- Classifications |
-
-
- |
-
-
- OWASP Category |
-
- OWASP API2:2023 Broken Authentication
- |
-
+
+ Severity |
+ High |
+
+
+ CVEs |
+
+ * [CVE-2020-28042](https://www.cve.org/CVERecord?id=CVE-2020-28042)
+ |
+
+
+ Classifications |
+
+ [CWE-327: Use of a Broken or Risky Cryptographic Algorithm](https://cwe.mitre.org/data/definitions/327.html)
+ |
+
+
+ OWASP Category |
+
+ [OWASP API2:2023 Broken Authentication](https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/)
+ |
+
The "JWT Null Signature" vulnerability occurs when a JSON Web Token (JWT) lacks a signature part, allowing attackers to manipulate the token's content potentially leading to unauthorized access and data tampering.
@@ -78,7 +74,7 @@ If you want to test only the "JWT Null Signature" vulnerability, you can use the
```bash copy
-echo "vulnapi scan curl [url] -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.null_signature
+vulnapi scan curl [url] -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.null_signature
```
@@ -88,7 +84,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..fee2d4e5 100644
--- a/docs/vulnerabilities/broken-authentication/jwt-weak-secret.mdx
+++ b/docs/vulnerabilities/broken-authentication/jwt-weak-secret.mdx
@@ -7,35 +7,31 @@ import { Tabs } from 'nextra/components'
# JWT Weak Secret
-
- Severity |
- High |
-
-
- CVEs |
-
-
- |
-
-
- Classifications |
-
-
- |
-
-
- OWASP Category |
-
- OWASP API2:2023 Broken Authentication
- |
-
+
+ Severity |
+ High |
+
+
+ CVEs |
+
+ * [CVE-2023-27172](https://nvd.nist.gov/vuln/detail/CVE-2023-27172)
+ * [CVE-2023-46943](https://nvd.nist.gov/vuln/detail/CVE-2023-46943)
+ |
+
+
+ Classifications |
+
+ * [CWE-287: Improper Authentication](https://cwe.mitre.org/data/definitions/287.html)
+ * [CWE-307: Improper Restriction of Excessive Authentication Attempts](https://cwe.mitre.org/data/definitions/307.html)
+ * [CWE-798: Use of Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html)
+ |
+
+
+ OWASP Category |
+
+ [OWASP API2:2023 Broken Authentication](https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/)
+ |
+
A vulnerability occurs when a JSON Web Token (JWT) is signed with a common, a well-known, or a weak secret. In this scenario, the token lacks proper cryptographic protection, making it susceptible to manipulation. Attackers can find the secret then modify the token's claims and content without detection, potentially leading to unauthorized access and data tampering.
@@ -85,7 +81,7 @@ If you want to test only the "JWT Null Signature" vulnerability, you can use the
```bash copy
-echo "vulnapi scan curl [url] -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.weak_secret
+vulnapi scan curl [url] -H "Authorization: Bearer eyJhbGciOiJSUzUxMiI..." --scans jwt.weak_secret
```
@@ -95,7 +91,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.mdx
similarity index 90%
rename from docs/vulnerabilities/security-misconfiguration/graphql-introspection.md
rename to docs/vulnerabilities/security-misconfiguration/graphql-introspection.mdx
index 1ddbded7..ee48c2be 100644
--- a/docs/vulnerabilities/security-misconfiguration/graphql-introspection.md
+++ b/docs/vulnerabilities/security-misconfiguration/graphql-introspection.mdx
@@ -16,15 +16,13 @@ description: GraphQL introspection is a feature that allows clients to query the
Classifications |
-
+ [CWE-200: Information Exposure](https://cwe.mitre.org/data/definitions/200.html)
|
OWASP Category |
- OWASP API8:2023 Security Misconfiguration
+ [OWASP API8:2023 Security Misconfiguration](https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/)
|
diff --git a/docs/vulnerabilities/security-misconfiguration/http-method-allow-override.mdx b/docs/vulnerabilities/security-misconfiguration/http-method-allow-override.mdx
new file mode 100644
index 00000000..e27bc077
--- /dev/null
+++ b/docs/vulnerabilities/security-misconfiguration/http-method-allow-override.mdx
@@ -0,0 +1,78 @@
+---
+description: HTTP Method Override is a feature that allows clients to override the default HTTP method used in a request. This feature can be exploited by attackers to bypass security controls and perform unauthorized actions on the server.
+---
+
+import { Tabs } from 'nextra/components'
+
+# HTTP Method Override Enabled
+
+
+
+ Severity |
+ Low - High |
+
+
+ CVEs |
+
+ * [CVE-2023-30845](https://www.cve.org/CVERecord?id=CVE-2023-30845)
+ * [CVE-2023-29003](https://www.cve.org/CVERecord?id=CVE-2023-29003)
+ * [CVE-2019-19326](https://www.cve.org/CVERecord?id=CVE-2019-19326)
+ |
+
+
+ Classifications |
+
+ [CWE-287: Improper Authentication](https://cwe.mitre.org/data/definitions/287.html)
+ |
+
+
+ OWASP Category |
+
+ [OWASP API8:2023 Security Misconfiguration](https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/)
+ |
+
+
+
+HTTP Method Override is a feature that allows clients to override the default HTTP method used in a request. This feature is commonly used to perform actions on the server using HTTP methods other than GET and POST, such as PUT, DELETE, PATCH, etc. The HTTP Method Override feature is typically implemented using custom headers or query parameters that specify the desired HTTP method to be used.
+
+This feature can be exploited by attackers to bypass security controls and perform unauthorized actions on the server.
+
+## 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
+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 API or intermediate proxy. You can do this by configuring the server to only accept the expected HTTP methods (GET, POST, PUT, DELETE, etc.) and reject any other methods that are not explicitly allowed.
+
+If you can not disable the HTTP Method Override behavior, ensure you implement proper access controls and validation checks to prevent unauthorized actions and that controls are not impacted by the method overriden. To do so, the usual way is to perform the checks before the method override is applied.
+
+## References
+
+- [X-HTTP-Method](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-odata/bdbabfa6-8c4a-4741-85a9-8d93ffd66c41)
+- [HTTP Method Override Blog Post](https://www.sidechannel.blog/en/http-method-override-what-it-is-and-how-a-pentester-can-use-it/)
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..52d229b5
--- /dev/null
+++ b/scan/misconfiguration/http_method_override/http_method_override.go
@@ -0,0 +1,179 @@
+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).WithOperation(operation).WithSecurityScheme(securityScheme)
+ 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)
+ if methodAttempt != nil {
+ r.AddScanAttempt(methodAttempt)
+ }
+
+ if err == nil && methodAttempt.Response.StatusCode == http.StatusMethodNotAllowed {
+ break
+ }
+ }
+
+ if err != nil {
+ r.AddIssueReport(httpMethodOverrideIssueReport).AddIssueReport(httpMethodOverrideAuthenticationByPassIssueReport).End()
+ return r, err
+ }
+
+ if methodAttempt.Response.StatusCode == initialAttempt.Response.StatusCode {
+ r.AddIssueReport(httpMethodOverrideIssueReport.Pass()).AddIssueReport(httpMethodOverrideAuthenticationByPassIssueReport.Skip()).End()
+ return r, nil
+ }
+
+ 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)
+ if attempt != nil {
+ r.AddScanAttempt(attempt)
+ }
+
+ if err == nil && attempt.Response.StatusCode == initialAttempt.Response.StatusCode {
+ attemptFailed = true
+ break
+ }
+ }
+
+ if !attemptFailed {
+ for _, queryParam := range methodOverrideQueryParams {
+ newOperation = operation.Clone()
+ newOperationQueryValues := newOperation.URL.Query()
+ newOperationQueryValues.Set(queryParam, operation.Method)
+ newOperation.URL.RawQuery = newOperationQueryValues.Encode()
+ newOperation.Method = newOperationMethod
+ attempt, err = scan.ScanURL(newOperation, &securityScheme)
+ if attempt != nil {
+ 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..c1c9ac95
--- /dev/null
+++ b/scan/misconfiguration/http_method_override/http_method_override_test.go
@@ -0,0 +1,204 @@
+package httpmethodoverride_test
+
+import (
+ "net/http"
+ "net/url"
+ "testing"
+
+ "github.com/cerberauth/vulnapi/internal/auth"
+ "github.com/cerberauth/vulnapi/internal/request"
+ "github.com/cerberauth/vulnapi/jwt"
+ "github.com/cerberauth/vulnapi/report"
+ 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_When_Error(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))
+
+ r, err := httpmethodoverride.ScanHandler(operation, securityScheme)
+
+ require.Error(t, err)
+ assert.Equal(t, 1, httpmock.GetTotalCallCount())
+ assert.Equal(t, 2, len(r.Issues))
+ assert.False(t, r.HasFailedIssueReport())
+ assert.Equal(t, r.Issues[0].Status, report.IssueReportStatusNone)
+ assert.Equal(t, r.Issues[1].Status, report.IssueReportStatusNone)
+}
+
+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.MethodHead, operation.URL.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil))
+ httpmock.RegisterResponder(http.MethodPost, operation.URL.String(), httpmock.NewBytesResponder(http.StatusMethodNotAllowed, nil))
+
+ report, err := httpmethodoverride.ScanHandler(operation, securityScheme)
+
+ require.NoError(t, err)
+ assert.Equal(t, 12, httpmock.GetTotalCallCount())
+ assert.Equal(t, 2, len(report.Issues))
+ assert.False(t, report.HasFailedIssueReport())
+ assert.True(t, report.Issues[0].HasPassed())
+ assert.True(t, report.Issues[1].HasBeenSkipped())
+}
+
+func TestHTTPMethodOverrideScanHandler_Failed_With_Header(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.MethodHead, operation.URL.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil))
+ httpmock.RegisterResponder(http.MethodPost, operation.URL.String(), func(req *http.Request) (*http.Response, error) {
+ if req.Header.Get("X-HTTP-Method-Override") == http.MethodGet {
+ return httpmock.NewBytesResponse(http.StatusNoContent, nil), nil
+ }
+ return httpmock.NewJsonResponse(http.StatusMethodNotAllowed, nil)
+ })
+
+ report, err := httpmethodoverride.ScanHandler(operation, securityScheme)
+
+ require.NoError(t, err)
+ assert.Equal(t, 4, httpmock.GetTotalCallCount())
+ assert.Equal(t, 2, len(report.Issues))
+ assert.True(t, report.HasFailedIssueReport())
+ assert.True(t, report.Issues[0].HasFailed())
+ assert.True(t, report.Issues[1].HasBeenSkipped())
+}
+
+func TestHTTPMethodOverrideScanHandler_Failed_With_Query_Parameter(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.MethodHead, operation.URL.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil))
+ httpmock.RegisterResponder(http.MethodPost, operation.URL.String(), httpmock.NewBytesResponder(http.StatusMethodNotAllowed, nil))
+
+ urlWithOverrideQuery, _ := url.Parse(operation.URL.String())
+ newQueryValues := urlWithOverrideQuery.Query()
+ newQueryValues.Set("_method", http.MethodGet)
+ urlWithOverrideQuery.RawQuery = newQueryValues.Encode()
+ httpmock.RegisterResponder(http.MethodPost, urlWithOverrideQuery.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil))
+
+ report, err := httpmethodoverride.ScanHandler(operation, securityScheme)
+
+ require.NoError(t, err)
+ assert.Equal(t, 9, httpmock.GetTotalCallCount())
+ assert.Equal(t, 2, len(report.Issues))
+ assert.True(t, report.HasFailedIssueReport())
+ assert.True(t, report.Issues[0].HasFailed())
+ assert.True(t, report.Issues[1].HasBeenSkipped())
+}
+
+func TestHTTPMethodOverrideScanHandler_Authentication_ByPass_Passed(t *testing.T) {
+ client := request.DefaultClient
+ httpmock.ActivateNonDefault(client.Client)
+ defer httpmock.DeactivateAndReset()
+
+ token := jwt.FakeJWT
+ securityScheme := auth.NewAuthorizationBearerSecurityScheme("securityScheme", &token)
+ 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.MethodHead, operation.URL.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil))
+ httpmock.RegisterResponder(http.MethodPost, operation.URL.String(), func(req *http.Request) (*http.Response, error) {
+ if req.Header.Get("X-HTTP-Method-Override") == http.MethodGet && req.Header.Get("Authorization") == "Bearer "+string(token) {
+ return httpmock.NewBytesResponse(http.StatusNoContent, nil), nil
+ }
+ if req.Header.Get("X-HTTP-Method-Override") == http.MethodGet && req.Header.Get("Authorization") == "" {
+ return httpmock.NewJsonResponse(http.StatusUnauthorized, nil)
+ }
+ return httpmock.NewJsonResponse(http.StatusMethodNotAllowed, nil)
+ })
+
+ report, err := httpmethodoverride.ScanHandler(operation, securityScheme)
+
+ require.NoError(t, err)
+ assert.Equal(t, 5, httpmock.GetTotalCallCount())
+ assert.Equal(t, 2, len(report.Issues))
+ assert.True(t, report.HasFailedIssueReport())
+ assert.True(t, report.Issues[0].HasFailed())
+ assert.True(t, report.Issues[1].HasPassed())
+}
+
+func TestHTTPMethodOverrideScanHandler_Authentication_ByPass_Failed(t *testing.T) {
+ client := request.DefaultClient
+ httpmock.ActivateNonDefault(client.Client)
+ defer httpmock.DeactivateAndReset()
+
+ token := jwt.FakeJWT
+ securityScheme := auth.NewAuthorizationBearerSecurityScheme("securityScheme", &token)
+ 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.MethodHead, operation.URL.String(), httpmock.NewBytesResponder(http.StatusNoContent, nil))
+ httpmock.RegisterResponder(http.MethodPost, operation.URL.String(), func(req *http.Request) (*http.Response, error) {
+ if req.Header.Get("X-HTTP-Method-Override") == http.MethodGet {
+ return httpmock.NewBytesResponse(http.StatusNoContent, nil), nil
+ }
+ return httpmock.NewJsonResponse(http.StatusMethodNotAllowed, nil)
+ })
+
+ report, err := httpmethodoverride.ScanHandler(operation, securityScheme)
+
+ require.NoError(t, err)
+ assert.Equal(t, 5, httpmock.GetTotalCallCount())
+ assert.Equal(t, 2, len(report.Issues))
+ assert.True(t, report.HasFailedIssueReport())
+ assert.True(t, report.Issues[0].HasFailed())
+ assert.True(t, report.Issues[1].HasFailed())
+}
diff --git a/scenario/scans.go b/scenario/scans.go
index 2ff598a1..42117eaa 100644
--- a/scenario/scans.go
+++ b/scenario/scans.go
@@ -12,6 +12,7 @@ import (
fingerprint "github.com/cerberauth/vulnapi/scan/discover/fingerprint"
httpcookies "github.com/cerberauth/vulnapi/scan/misconfiguration/http_cookies"
httpheaders "github.com/cerberauth/vulnapi/scan/misconfiguration/http_headers"
+ httpmethodoverride "github.com/cerberauth/vulnapi/scan/misconfiguration/http_method_override"
httptrace "github.com/cerberauth/vulnapi/scan/misconfiguration/http_trace"
httptrack "github.com/cerberauth/vulnapi/scan/misconfiguration/http_track"
)
@@ -29,6 +30,7 @@ func WithAllCommonScans(s *scan.Scan) *scan.Scan {
s.AddOperationScanHandler(scan.NewOperationScanHandler(httpcookies.HTTPCookiesScanID, httpcookies.ScanHandler))
s.AddOperationScanHandler(scan.NewOperationScanHandler(httpheaders.HTTPHeadersScanID, httpheaders.ScanHandler))
+ s.AddOperationScanHandler(scan.NewOperationScanHandler(httpmethodoverride.HTTPMethodOverrideScanID, httpmethodoverride.ScanHandler))
s.AddOperationScanHandler(scan.NewOperationScanHandler(httptrace.HTTPTraceScanID, httptrace.ScanHandler))
s.AddOperationScanHandler(scan.NewOperationScanHandler(httptrack.HTTPTrackScanID, httptrack.ScanHandler))