Skip to content

Commit

Permalink
feat: add http method override scan
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelgautier committed Oct 21, 2024
1 parent ed5d68b commit c7408c9
Show file tree
Hide file tree
Showing 17 changed files with 428 additions and 12 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/scans.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion docs/vulnerabilities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 | |
Expand All @@ -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 ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan openapi [OpenAPI_Path_Or_URL] --sca
</Tabs.Tab>
<Tabs.Tab>
```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]
```
</Tabs.Tab>
</Tabs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan openapi [OpenAPI_Path_Or_URL] --sca
</Tabs.Tab>
<Tabs.Tab>
```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]
```
</Tabs.Tab>
</Tabs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan openapi [OpenAPI_Path_Or_URL] --sca
</Tabs.Tab>
<Tabs.Tab>
```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]
```
</Tabs.Tab>
</Tabs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ echo "eyJhbGciOiJSUzUxMiI..." | vulnapi scan openapi [OpenAPI_Path_Or_URL] --sca
</Tabs.Tab>
<Tabs.Tab>
```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]
```
</Tabs.Tab>
</Tabs>
Expand Down
3 changes: 3 additions & 0 deletions docs/vulnerabilities/security-misconfiguration/_meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"graphql-introspection": {
"title": "GraphQL Introspection Enabled"
},
"http-method-allow-override": {
"title": "HTTP Method Override Enabled"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ description: GraphQL introspection is a feature that allows clients to query the
<tr>
<th>OWASP Category</th>
<td>
<a href="https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/">OWASP API8:2023 Security Misconfiguration</a>
<a href="https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/">OWASP API8:2023 Security Misconfiguration</a>
</td>
</tr>
</table>
Expand Down
Original file line number Diff line number Diff line change
@@ -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

<table>
<tr>
<th>Severity</th>
<td>Low - High</td>
</tr>
<tr>
<th>CVEs</th>
<td>
<ul>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2023-30845">CVE-2023-30845</a></li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2023-29003">CVE-2023-29003</a></li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2019-19326">CVE-2019-19326</a></li>
</ul>
</td>
</tr>
<tr>
<th>Classifications</th>
<td>
<ul>
<a href="https://cwe.mitre.org/data/definitions/287.html">CWE-287: Improper Authentication</a>
</ul>
</td>
</tr>
<tr>
<th>OWASP Category</th>
<td>
<a href="https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/">OWASP API8:2023 Security Misconfiguration</a>
</td>
</tr>
</table>

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:

<Tabs items={['cURL', 'OpenAPI', 'GraphQL']}>
<Tabs.Tab>
```bash copy
echo "vulnapi scan curl [url] --scans misconfiguration.http_method_override
```
</Tabs.Tab>
<Tabs.Tab>
```bash copy
vulnapi scan openapi [OpenAPI_Path_Or_URL] --scans misconfiguration.http_method_override
```
</Tabs.Tab>
<Tabs.Tab>
```bash copy
vulnapi scan graphql --scans misconfiguration.http_method_override [url]
```
</Tabs.Tab>
</Tabs>
## 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]
8 changes: 8 additions & 0 deletions internal/auth/jwt_bearer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
37 changes: 37 additions & 0 deletions internal/auth/jwt_bearer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions internal/request/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
25 changes: 25 additions & 0 deletions internal/request/operation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions report/cwe.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
12 changes: 6 additions & 6 deletions scan/broken_authentication/jwt/alg_none/alg_none.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}
Expand Down
Loading

0 comments on commit c7408c9

Please sign in to comment.