Skip to content

Commit

Permalink
Merge pull request #84 from cerberauth/introspection-endpoint-get-method
Browse files Browse the repository at this point in the history
feat: scan introspection endpoint with get method
  • Loading branch information
emmanuelgautier authored Apr 11, 2024
2 parents dd3d989 + b6aca30 commit 861b1c1
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 13 deletions.
48 changes: 42 additions & 6 deletions scan/discover/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,24 @@ var potentialGraphQLEndpoints = []string{
"/v1/graphiql",
"/v1/explorer",
}

const graphqlQuery = `{
"query": "query{__schema
{queryType{name}}}"
}`

var graphqlSeclistUrl = "https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/graphql.txt"

func newGraphqlIntrospectionRequest(endpoint *url.URL) (*http.Request, error) {
return http.NewRequest(http.MethodPost, endpoint.String(), bytes.NewReader([]byte(`{
"query": "query{__schema
{queryType{name}}}"
}`)))
func newPostGraphqlIntrospectionRequest(endpoint *url.URL) (*http.Request, error) {
return http.NewRequest(http.MethodPost, endpoint.String(), bytes.NewReader([]byte(graphqlQuery)))
}

func newGetGraphqlIntrospectionRequest(endpoint *url.URL) (*http.Request, error) {
values := url.Values{}
values.Add("query", graphqlQuery)
endpoint.RawQuery = values.Encode()

return http.NewRequest(http.MethodGet, endpoint.String(), nil)
}

func GraphqlIntrospectionScanHandler(operation *request.Operation, securityScheme auth.SecurityScheme) (*report.ScanReport, error) {
Expand All @@ -47,7 +58,32 @@ func GraphqlIntrospectionScanHandler(operation *request.Operation, securitySchem
base := ExtractBaseURL(operation.Request.URL)

for _, path := range potentialGraphQLEndpoints {
newRequest, err := newGraphqlIntrospectionRequest(base.ResolveReference(&url.URL{Path: path}))
newRequest, err := newPostGraphqlIntrospectionRequest(base.ResolveReference(&url.URL{Path: path}))
if err != nil {
return r, err
}

newOperation := request.NewOperationFromRequest(newRequest, []auth.SecurityScheme{securityScheme})
attempt, err := scan.ScanURL(newOperation, &securityScheme)
r.AddScanAttempt(attempt).End()
if err != nil {
return r, err
}

if attempt.Response.StatusCode < 300 {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: GraphqlIntrospectionEnabledSeverityLevel,
Name: GraphqlIntrospectionEnabledVulnerabilityName,
Description: GraphqlIntrospectionEnabledVulnerabilityDescription,
Operation: operation,
})

return r, nil
}
}

for _, path := range potentialGraphQLEndpoints {
newRequest, err := newGetGraphqlIntrospectionRequest(base.ResolveReference(&url.URL{Path: path}))
if err != nil {
return r, err
}
Expand Down
58 changes: 52 additions & 6 deletions scan/discover/graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,26 @@ func TestGraphqlIntrospectionScanHandler(t *testing.T) {
securityScheme := auth.NewNoAuthSecurityScheme()
operation := request.NewOperation("http://localhost:8080", http.MethodPost, nil, nil, nil)

httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil).HeaderAdd(http.Header{"Server": []string{"Apache/2.4.29 (Ubuntu)"}}))
httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil))
httpmock.RegisterNoResponder(func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(404, "Not Found"), nil
})

report, err := discover.GraphqlIntrospectionScanHandler(operation, securityScheme)

require.NoError(t, err)
assert.Greater(t, httpmock.GetTotalCallCount(), 1)
assert.False(t, report.HasVulnerabilityReport())
}

func TestGetGraphqlIntrospectionScanHandler(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

securityScheme := auth.NewNoAuthSecurityScheme()
operation := request.NewOperation("http://localhost:8080", http.MethodGet, nil, nil, nil)

httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil))
httpmock.RegisterNoResponder(func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(404, "Not Found"), nil
})
Expand All @@ -38,7 +57,34 @@ func TestGraphqlIntrospectionScanHandlerWithKnownGraphQLIntrospectionEndpoint(t

securityScheme := auth.NewNoAuthSecurityScheme()
operation := request.NewOperation("http://localhost:8080/graphql", http.MethodPost, nil, nil, nil)
httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil).HeaderAdd(http.Header{"Server": []string{"Apache/2.4.29 (Ubuntu)"}}))
httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil))
httpmock.RegisterNoResponder(func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(404, "Not Found"), nil
})

expectedReport := report.VulnerabilityReport{
SeverityLevel: discover.GraphqlIntrospectionEnabledSeverityLevel,
Name: discover.GraphqlIntrospectionEnabledVulnerabilityName,
Description: discover.GraphqlIntrospectionEnabledVulnerabilityDescription,
Operation: operation,
}

report, err := discover.GraphqlIntrospectionScanHandler(operation, securityScheme)

require.NoError(t, err)
assert.Greater(t, httpmock.GetTotalCallCount(), 0)
assert.True(t, report.HasVulnerabilityReport())
assert.Equal(t, report.GetVulnerabilityReports()[0].Name, expectedReport.Name)
assert.Equal(t, report.GetVulnerabilityReports()[0].Operation.Request.URL.String(), expectedReport.Operation.Request.URL.String())
}

func TestGetGraphqlIntrospectionScanHandlerWithKnownGraphQLIntrospectionEndpoint(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

securityScheme := auth.NewNoAuthSecurityScheme()
operation := request.NewOperation("http://localhost:8080/graphql", http.MethodGet, nil, nil, nil)
httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil))
httpmock.RegisterNoResponder(func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(404, "Not Found"), nil
})
Expand All @@ -64,9 +110,9 @@ func TestDiscoverableScannerWithNoDiscoverableGraphqlPath(t *testing.T) {
defer httpmock.DeactivateAndReset()

securityScheme := auth.NewNoAuthSecurityScheme()
operation := request.NewOperation("http://localhost:8080/", "GET", nil, nil, nil)
operation := request.NewOperation("http://localhost:8080/", http.MethodGet, nil, nil, nil)

httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil).HeaderAdd(http.Header{"Server": []string{"Apache/2.4.29 (Ubuntu)"}}))
httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil))
httpmock.RegisterNoResponder(func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(404, "Not Found"), nil
})
Expand All @@ -83,8 +129,8 @@ func TestDiscoverableScannerWithOneDiscoverableGraphQLPath(t *testing.T) {
defer httpmock.DeactivateAndReset()

securityScheme := auth.NewNoAuthSecurityScheme()
operation := request.NewOperation("http://localhost:8080/graphql", "GET", nil, nil, nil)
httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil).HeaderAdd(http.Header{"Server": []string{"Apache/2.4.29 (Ubuntu)"}}))
operation := request.NewOperation("http://localhost:8080/graphql", http.MethodGet, nil, nil, nil)
httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(204, nil))
httpmock.RegisterNoResponder(func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(404, "Not Found"), nil
})
Expand Down
2 changes: 1 addition & 1 deletion scan/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func NewGraphQLScan(url string, header http.Header, cookies []http.Cookie, repor
securitySchemes = append(securitySchemes, auth.NewNoAuthSecurityScheme())
}

operations := request.Operations{request.NewOperation(url, "POST", header, cookies, securitySchemes)}
operations := request.Operations{request.NewOperation(url, http.MethodPost, header, cookies, securitySchemes)}

return NewScan(operations, reporter)
}

0 comments on commit 861b1c1

Please sign in to comment.