From d704906d51763fad96c149e0c58d27f0c8503237 Mon Sep 17 00:00:00 2001 From: nohehf Date: Tue, 14 Mar 2023 14:33:50 -0700 Subject: [PATCH 1/5] feat: first engines with naive detection --- pkg/engine/adriane.go | 15 +++++++++++++++ pkg/engine/apollo.go | 21 +++++++++++++++++++++ pkg/engine/engines.go | 35 +++++++++++++++++++++++++++++++++++ pkg/fingerprint/engine.go | 27 +++++++++++++++++++++++++++ pkg/output/output.go | 1 + 5 files changed, 99 insertions(+) create mode 100644 pkg/engine/adriane.go create mode 100644 pkg/engine/apollo.go create mode 100644 pkg/engine/engines.go create mode 100644 pkg/fingerprint/engine.go diff --git a/pkg/engine/adriane.go b/pkg/engine/adriane.go new file mode 100644 index 0000000..473bfcd --- /dev/null +++ b/pkg/engine/adriane.go @@ -0,0 +1,15 @@ +package engine + +var Adriane = &engine{ + Name: "adriane", + Imprints: []imprint{ + { + Query: "", + Matcher: inResponseText([]string{"The query must be a string."}), + }, + { + Query: "query { __typename @abc }", + Matcher: inResponseText([]string{"Unknown directive '@abc'."}), + }, + }, +} diff --git a/pkg/engine/apollo.go b/pkg/engine/apollo.go new file mode 100644 index 0000000..3513bff --- /dev/null +++ b/pkg/engine/apollo.go @@ -0,0 +1,21 @@ +package engine + +var Apollo = &engine{ + Name: "apollo", + Imprints: []imprint{ + { + Query: "query @deprecated { __typename }", + Matcher: inResponseText([]string{ + "Directive \\\"@deprecated\\\" may not be used on QUERY.", + "Directive \\\"deprecated\\\" may not be used on QUERY.", + }), + }, + { + Query: "query @skip { __typename }", + Matcher: inResponseText([]string{ + "Directive \\\"@skip\\\" argument \\\"if\\\" of type \\\"Boolean!\\\" is required, but it was not provided", + "Directive \\\"skip\\\" argument \\\"if\\\" of type \\\"Boolean!\\\" is required, but it was not provided", + }), + }, + }, +} diff --git a/pkg/engine/engines.go b/pkg/engine/engines.go new file mode 100644 index 0000000..aeec598 --- /dev/null +++ b/pkg/engine/engines.go @@ -0,0 +1,35 @@ +package engine + +import "bytes" + +type engine struct { + Name string + Imprints []imprint +} + +type imprint struct { + Query string + Matcher matcher +} + +// A responseMatcher is a function that takes a response body and returns true if the response matches the engine. +type matcher func(responseBody *[]byte) bool + +// inResponseText returns a responseMatcher that checks if the response body contains any of the given strings. +func inResponseText(matches []string) matcher { + return func(responseBody *[]byte) bool { + for _, match := range matches { + if bytes.Contains(*responseBody, []byte(match)) { + return true + } + } + return false + } +} + +// Order is important here, as the first match will be returned. +// The order has been determined by the usage statistics of the engines. (The higher the usage, the higher the priority.) +var Engines = []*engine{ + Apollo, + Adriane, +} diff --git a/pkg/fingerprint/engine.go b/pkg/fingerprint/engine.go new file mode 100644 index 0000000..57b55cf --- /dev/null +++ b/pkg/fingerprint/engine.go @@ -0,0 +1,27 @@ +package fingerprint + +import ( + "github.com/Escape-Technologies/goctopus/internal/http" + "github.com/Escape-Technologies/goctopus/pkg/engine" + log "github.com/sirupsen/logrus" +) + +func (fp *fingerprinter) Engine() (string, error) { + + for _, ngin := range engine.Engines { + for _, imprint := range ngin.Imprints { + log.Debugf("Trying to match %s with %s", imprint.Query, ngin.Name) + requestBody := http.GraphqlBodyPayload(imprint.Query) + resp, err := fp.Client.Post(fp.url, []byte(requestBody)) + log.Debugf("Response: %s", resp.Body) + if err != nil { + return "", err + } + if imprint.Matcher(resp.Body) { + return ngin.Name, nil + } + } + } + + return "unknown", nil +} diff --git a/pkg/output/output.go b/pkg/output/output.go index ade8bfc..9e01fd3 100644 --- a/pkg/output/output.go +++ b/pkg/output/output.go @@ -22,6 +22,7 @@ type FingerprintOutput struct { SchemaStatus SchemaStatus `json:"schema_status"` Source string `json:"source"` // the original address used to fingerprint the endpoint Metadata map[string]string `json:"metadata"` // optional metadata + Engine string `json:"engine"` } func (o *FingerprintOutput) MarshalJSON() ([]byte, error) { From 4fd6ecead0ca3017a64c795a95b748b5ede15fd8 Mon Sep 17 00:00:00 2001 From: nohehf Date: Tue, 14 Mar 2023 15:29:18 -0700 Subject: [PATCH 2/5] --wip-- [skip ci] --- pkg/engine/TEMPLATE.go | 11 +++++ pkg/engine/_agoo.go | 11 +++++ pkg/engine/adriane.go | 2 +- pkg/engine/apollo.go | 2 +- pkg/engine/aws_app_sync.go | 11 +++++ pkg/engine/engine.go | 72 +++++++++++++++++++++++++++++++++ pkg/engine/engines.go | 35 ---------------- pkg/engine/graphene.go | 11 +++++ pkg/engine/graphql_go.go | 19 +++++++++ pkg/engine/graphql_gopher_go.go | 11 +++++ pkg/engine/graphql_java.go | 19 +++++++++ pkg/engine/graphql_php.go | 15 +++++++ pkg/engine/ruby.go | 23 +++++++++++ 13 files changed, 205 insertions(+), 37 deletions(-) create mode 100644 pkg/engine/TEMPLATE.go create mode 100644 pkg/engine/_agoo.go create mode 100644 pkg/engine/aws_app_sync.go create mode 100644 pkg/engine/engine.go delete mode 100644 pkg/engine/engines.go create mode 100644 pkg/engine/graphene.go create mode 100644 pkg/engine/graphql_go.go create mode 100644 pkg/engine/graphql_gopher_go.go create mode 100644 pkg/engine/graphql_java.go create mode 100644 pkg/engine/graphql_php.go create mode 100644 pkg/engine/ruby.go diff --git a/pkg/engine/TEMPLATE.go b/pkg/engine/TEMPLATE.go new file mode 100644 index 0000000..fc4986a --- /dev/null +++ b/pkg/engine/TEMPLATE.go @@ -0,0 +1,11 @@ +package engine + +var T = &engine{ + Name: "", + Imprints: []imprint{ + { + Query: "", + Matcher: inResponseText([]string{""}), + }, + }, +} diff --git a/pkg/engine/_agoo.go b/pkg/engine/_agoo.go new file mode 100644 index 0000000..3dacd80 --- /dev/null +++ b/pkg/engine/_agoo.go @@ -0,0 +1,11 @@ +package engine + +var Agoo = &engine{ + Name: "agoo", + Imprints: []imprint{ + { + Query: "query { zzz }", + Matcher: inSection("code", []string{"eval error"}), + }, + }, +} diff --git a/pkg/engine/adriane.go b/pkg/engine/adriane.go index 473bfcd..33fa267 100644 --- a/pkg/engine/adriane.go +++ b/pkg/engine/adriane.go @@ -1,7 +1,7 @@ package engine var Adriane = &engine{ - Name: "adriane", + Name: "Adriane", Imprints: []imprint{ { Query: "", diff --git a/pkg/engine/apollo.go b/pkg/engine/apollo.go index 3513bff..abe2b56 100644 --- a/pkg/engine/apollo.go +++ b/pkg/engine/apollo.go @@ -1,7 +1,7 @@ package engine var Apollo = &engine{ - Name: "apollo", + Name: "Apollo", Imprints: []imprint{ { Query: "query @deprecated { __typename }", diff --git a/pkg/engine/aws_app_sync.go b/pkg/engine/aws_app_sync.go new file mode 100644 index 0000000..a685969 --- /dev/null +++ b/pkg/engine/aws_app_sync.go @@ -0,0 +1,11 @@ +package engine + +var AWSAppSync = &engine{ + Name: "AWSAppSync", + Imprints: []imprint{ + { + Query: "query @skip { __typename }", + Matcher: inResponseText([]string{"MisplacedDirective"}), + }, + }, +} diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go new file mode 100644 index 0000000..861a2c0 --- /dev/null +++ b/pkg/engine/engine.go @@ -0,0 +1,72 @@ +package engine + +import ( + "bytes" + "encoding/json" +) + +type engine struct { + Name string + Imprints []imprint +} + +type imprint struct { + Query string + Matcher matcher +} + +// A responseMatcher is a function that takes a response body and returns true if the response matches the engine. +type matcher func(responseBody *[]byte) bool + +// inResponseText returns a responseMatcher that checks if the response body contains any of the given strings. +func inResponseText(matches []string) matcher { + return func(responseBody *[]byte) bool { + for _, match := range matches { + if bytes.Contains(*responseBody, []byte(match)) { + return true + } + } + return false + } +} + +// inSection returns a responseMatcher that checks if the response body contains any of the given strings in the given section. +func inSection(section string, matches []string) matcher { + return func(responseBody *[]byte) bool { + var reponseBody map[string]interface{} + json.Unmarshal(*responseBody, &reponseBody) + content, err := json.Marshal(reponseBody[section]) + if err != nil { + return false + } + for _, match := range matches { + if bytes.Contains(content, []byte(match)) { + return true + } + } + return false + } +} + +// hasJsonKey returns a responseMatcher that checks if the response body contains the given key. +func hasJsonKey(key string) matcher { + return func(responseBody *[]byte) bool { + var reponseBody map[string]interface{} + json.Unmarshal(*responseBody, &reponseBody) + _, ok := reponseBody[key] + return ok + } +} + +// Order is important here, as the first match will be returned. +// The order has been determined by the usage statistics of the engines. (The higher the usage, the higher the priority.) +var Engines = []*engine{ + Apollo, + AWSAppSync, + GraphQLGo, + Ruby, + GraphQLPHP, + Graphene, + Adriane, + GraphQLGopherGo, +} diff --git a/pkg/engine/engines.go b/pkg/engine/engines.go deleted file mode 100644 index aeec598..0000000 --- a/pkg/engine/engines.go +++ /dev/null @@ -1,35 +0,0 @@ -package engine - -import "bytes" - -type engine struct { - Name string - Imprints []imprint -} - -type imprint struct { - Query string - Matcher matcher -} - -// A responseMatcher is a function that takes a response body and returns true if the response matches the engine. -type matcher func(responseBody *[]byte) bool - -// inResponseText returns a responseMatcher that checks if the response body contains any of the given strings. -func inResponseText(matches []string) matcher { - return func(responseBody *[]byte) bool { - for _, match := range matches { - if bytes.Contains(*responseBody, []byte(match)) { - return true - } - } - return false - } -} - -// Order is important here, as the first match will be returned. -// The order has been determined by the usage statistics of the engines. (The higher the usage, the higher the priority.) -var Engines = []*engine{ - Apollo, - Adriane, -} diff --git a/pkg/engine/graphene.go b/pkg/engine/graphene.go new file mode 100644 index 0000000..fea6d52 --- /dev/null +++ b/pkg/engine/graphene.go @@ -0,0 +1,11 @@ +package engine + +var Graphene = &engine{ + Name: "Graphene", + Imprints: []imprint{ + { + Query: "query { aaa }", + Matcher: inResponseText([]string{"Syntax Error GraphQL (1:1)"}), + }, + }, +} diff --git a/pkg/engine/graphql_go.go b/pkg/engine/graphql_go.go new file mode 100644 index 0000000..263e0bb --- /dev/null +++ b/pkg/engine/graphql_go.go @@ -0,0 +1,19 @@ +package engine + +var GraphQLGo = &engine{ + Name: "GraphQLGo", + Imprints: []imprint{ + { + Query: "", + Matcher: inResponseText([]string{"Must provide an operation."}), + }, + { + Query: "query { __typename {}", + Matcher: inResponseText([]string{"Unexpected empty IN"}), + }, + { + Query: "query { __typename }", + Matcher: inResponseText([]string{"RootQuery"}), + }, + }, +} diff --git a/pkg/engine/graphql_gopher_go.go b/pkg/engine/graphql_gopher_go.go new file mode 100644 index 0000000..4a21dc4 --- /dev/null +++ b/pkg/engine/graphql_gopher_go.go @@ -0,0 +1,11 @@ +package engine + +var GraphQLGopherGo = &engine{ + Name: "", + Imprints: []imprint{ + { + Query: "query {}", + Matcher: hasJsonKey("data"), + }, + }, +} diff --git a/pkg/engine/graphql_java.go b/pkg/engine/graphql_java.go new file mode 100644 index 0000000..47fead9 --- /dev/null +++ b/pkg/engine/graphql_java.go @@ -0,0 +1,19 @@ +package engine + +var GraphQLJava = &engine{ + Name: "", + Imprints: []imprint{ + { + Query: "", + Matcher: inResponseText([]string{"Invalid Syntax : offending token ''"}), + }, + { + Query: "query @aaa@aaa { __typename }", + Matcher: inResponseText([]string{"Validation error of type DuplicateDirectiveName: Directives must be uniquely named within a location."}), + }, + { + Query: "queryy { __typename }", + Matcher: inResponseText([]string{"Invalid Syntax : offending token 'queryy'"}), + }, + }, +} diff --git a/pkg/engine/graphql_php.go b/pkg/engine/graphql_php.go new file mode 100644 index 0000000..f906957 --- /dev/null +++ b/pkg/engine/graphql_php.go @@ -0,0 +1,15 @@ +package engine + +var GraphQLPHP = &engine{ + Name: "GraphQLPHP", + Imprints: []imprint{ + { + Query: "query ! {__typename}", + Matcher: inResponseText([]string{"Syntax Error: Cannot parse the unexpected character \"?\"."}), + }, + { + Query: "query @deprecated {__typename}", + Matcher: inResponseText([]string{"Directive \"deprecated\" may not be used on \"QUERY\"."}), + }, + }, +} diff --git a/pkg/engine/ruby.go b/pkg/engine/ruby.go new file mode 100644 index 0000000..1f38971 --- /dev/null +++ b/pkg/engine/ruby.go @@ -0,0 +1,23 @@ +package engine + +var Ruby = &engine{ + Name: "Ruby", + Imprints: []imprint{ + { + Query: "query @deprecated { __typename }", + Matcher: inResponseText([]string{"'@deprecated' can't be applied to queries"}), + }, + { + Query: "query @skip { __typename }", + Matcher: inResponseText([]string{"'@skip' can't be applied to queries (allowed: fields, fragment spreads, inline fragments)"}), + }, + { + Query: "query { __typename @skip }", + Matcher: inResponseText([]string{"Directive 'skip' is missing required arguments: if"}), + }, + { + Query: "query { __typename {}", + Matcher: inResponseText([]string{"Parse error on \"}\" (RCURLY)"}), + }, + }, +} From f754867e5bd35b52ad1168b7b50e5e6dfccb8001 Mon Sep 17 00:00:00 2001 From: nohehf Date: Fri, 21 Jul 2023 10:20:39 +0200 Subject: [PATCH 3/5] feat: adapt to new architecture --- pkg/endpoint/endpoint.go | 6 ++++++ pkg/endpoint/fingerprint_test.go | 6 +++++- pkg/engine/{_agoo.go => agoo.go} | 0 pkg/engine/engine.go | 24 ++++++++++++++++++++++++ pkg/fingerprint/engine.go | 27 --------------------------- pkg/http/http.go | 5 +++++ 6 files changed, 40 insertions(+), 28 deletions(-) rename pkg/engine/{_agoo.go => agoo.go} (100%) delete mode 100644 pkg/fingerprint/engine.go diff --git a/pkg/endpoint/endpoint.go b/pkg/endpoint/endpoint.go index bd0d891..0816049 100644 --- a/pkg/endpoint/endpoint.go +++ b/pkg/endpoint/endpoint.go @@ -18,6 +18,7 @@ type endpointFingerprinter interface { IsAuthenticatedGraphql() (bool, error) HasFieldSuggestion() (bool, error) HasIntrospectionOpen() (bool, error) + GetEngine() string } func NewEndpointFingerprinter(url *address.Addr, client http.Client) endpointFingerprinter { @@ -42,3 +43,8 @@ func (e *_endpointFingerprinter) HasFieldSuggestion() (bool, error) { func (e *_endpointFingerprinter) HasIntrospectionOpen() (bool, error) { return introspection.FingerprintIntrospection(e.url.Address, e.client) } + +func (e *_endpointFingerprinter) GetEngine() string { + return "not implemented" + // return engines.FingerprintEngine(e.url.Address, e.client) +} diff --git a/pkg/endpoint/fingerprint_test.go b/pkg/endpoint/fingerprint_test.go index 3a7b70b..811fdcf 100644 --- a/pkg/endpoint/fingerprint_test.go +++ b/pkg/endpoint/fingerprint_test.go @@ -33,6 +33,10 @@ func (m *mockedEndpointFingerprinter) HasFieldSuggestion() (bool, error) { return m.fieldSuggestion, nil } +func (m *mockedEndpointFingerprinter) GetEngine() string { + return "" +} + func (m *mockedEndpointFingerprinter) Close() {} func makeMockedEndpointFingerprinter(graphql bool, introspection bool) *mockedEndpointFingerprinter { @@ -42,7 +46,7 @@ func makeMockedEndpointFingerprinter(graphql bool, introspection bool) *mockedEn } } -// @todo test field suggestion +// @todo test field suggestion & engine fingerprinting func TestFingerprintUrl(t *testing.T) { url := &address.Addr{ diff --git a/pkg/engine/_agoo.go b/pkg/engine/agoo.go similarity index 100% rename from pkg/engine/_agoo.go rename to pkg/engine/agoo.go diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 861a2c0..7c9031c 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -3,6 +3,10 @@ package engine import ( "bytes" "encoding/json" + + "github.com/Escape-Technologies/goctopus/pkg/http" + + log "github.com/sirupsen/logrus" ) type engine struct { @@ -63,6 +67,7 @@ func hasJsonKey(key string) matcher { var Engines = []*engine{ Apollo, AWSAppSync, + Agoo, GraphQLGo, Ruby, GraphQLPHP, @@ -70,3 +75,22 @@ var Engines = []*engine{ Adriane, GraphQLGopherGo, } + +func FingerprintEngine(url string, client http.Client) string { + for _, engine := range Engines { + for _, imprint := range engine.Imprints { + log.Debugf("Trying to match %s with %s", imprint.Query, engine.Name) + requestBody := http.QueryToRequestBody(imprint.Query) + resp, err := client.Post(url, []byte(requestBody)) + if err != nil { + log.Debugf("Error from %v: %v", url, err) + continue + } + log.Debugf("Response: %s", resp.Body) + if imprint.Matcher(resp.Body) { + return engine.Name + } + } + } + return "unknown" +} diff --git a/pkg/fingerprint/engine.go b/pkg/fingerprint/engine.go deleted file mode 100644 index 57b55cf..0000000 --- a/pkg/fingerprint/engine.go +++ /dev/null @@ -1,27 +0,0 @@ -package fingerprint - -import ( - "github.com/Escape-Technologies/goctopus/internal/http" - "github.com/Escape-Technologies/goctopus/pkg/engine" - log "github.com/sirupsen/logrus" -) - -func (fp *fingerprinter) Engine() (string, error) { - - for _, ngin := range engine.Engines { - for _, imprint := range ngin.Imprints { - log.Debugf("Trying to match %s with %s", imprint.Query, ngin.Name) - requestBody := http.GraphqlBodyPayload(imprint.Query) - resp, err := fp.Client.Post(fp.url, []byte(requestBody)) - log.Debugf("Response: %s", resp.Body) - if err != nil { - return "", err - } - if imprint.Matcher(resp.Body) { - return ngin.Name, nil - } - } - } - - return "unknown", nil -} diff --git a/pkg/http/http.go b/pkg/http/http.go index 61934e0..ad6213c 100644 --- a/pkg/http/http.go +++ b/pkg/http/http.go @@ -3,6 +3,7 @@ package http import ( "crypto/tls" "errors" + "fmt" "sync" "time" @@ -134,3 +135,7 @@ func SendToWebhook(url string, body []byte, wg *sync.WaitGroup) error { } return nil } + +func QueryToRequestBody(query string) []byte { + return []byte(fmt.Sprintf(`{"query":"%s"}`, query)) +} From a8360d05afb4df4a7b0da44ad3d00484060b96eb Mon Sep 17 00:00:00 2001 From: nohehf Date: Fri, 21 Jul 2023 11:03:45 +0200 Subject: [PATCH 4/5] feat: basic implementation --- pkg/config/config.go | 4 +++- pkg/endpoint/endpoint.go | 4 ++-- pkg/endpoint/fingerprint.go | 4 ++++ pkg/engine/engine.go | 27 ++++++++++++++++++++++++++- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 0bfe89c..805510a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -22,6 +22,7 @@ type Config struct { FieldSuggestion bool WebhookUrl string SubdomainEnumeration bool + EngineFingerprinting bool } var ( @@ -61,9 +62,10 @@ func LoadFromArgs() { flag.BoolVar(&config.Introspection, "introspect", false, "Enable introspection fingerprinting") flag.BoolVar(&config.FieldSuggestion, "suggest", false, "Enable fields suggestion fingerprinting.\nNeeds \"introspection\" to be enabled.") flag.BoolVar(&config.SubdomainEnumeration, "subdomain", false, "Enable subdomain enumeration") + flag.BoolVar(&config.EngineFingerprinting, "engine", false, "[Experimental] Enable GraphQL engine fingerprinting") // -a (All) flag enables all fingerprinting methods - all := flag.Bool("a", false, "(All) Enable all fingerprinting methods: introspection, field suggestion, subdomain enumeration") + all := flag.Bool("a", false, "(All) Enable all stable fingerprinting methods: introspection, field suggestion, subdomain enumeration") flag.Parse() diff --git a/pkg/endpoint/endpoint.go b/pkg/endpoint/endpoint.go index 0816049..6516ebc 100644 --- a/pkg/endpoint/endpoint.go +++ b/pkg/endpoint/endpoint.go @@ -2,6 +2,7 @@ package endpoint import ( "github.com/Escape-Technologies/goctopus/pkg/address" + "github.com/Escape-Technologies/goctopus/pkg/engine" "github.com/Escape-Technologies/goctopus/pkg/graphql" "github.com/Escape-Technologies/goctopus/pkg/http" "github.com/Escape-Technologies/goctopus/pkg/introspection" @@ -45,6 +46,5 @@ func (e *_endpointFingerprinter) HasIntrospectionOpen() (bool, error) { } func (e *_endpointFingerprinter) GetEngine() string { - return "not implemented" - // return engines.FingerprintEngine(e.url.Address, e.client) + return engine.FingerprintEngine(e.url.Address, e.client) } diff --git a/pkg/endpoint/fingerprint.go b/pkg/endpoint/fingerprint.go index 17c1d52..25a6608 100644 --- a/pkg/endpoint/fingerprint.go +++ b/pkg/endpoint/fingerprint.go @@ -23,6 +23,10 @@ func fingerprintEndpoint(url *address.Addr, e endpointFingerprinter, config *con return nil, err } + if config.EngineFingerprinting { + out.Engine = e.GetEngine() + } + if !isOpenGraphql { isAuthenticatedGraphql, err := e.IsAuthenticatedGraphql() if err != nil { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 7c9031c..73abdc8 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -76,7 +76,15 @@ var Engines = []*engine{ GraphQLGopherGo, } +func addScore(engineName string, scores map[string]int) { + if _, ok := scores[engineName]; !ok { + scores[engineName] = 0 + } + scores[engineName]++ +} + func FingerprintEngine(url string, client http.Client) string { + scores := make(map[string]int) // engine name -> score for _, engine := range Engines { for _, imprint := range engine.Imprints { log.Debugf("Trying to match %s with %s", imprint.Query, engine.Name) @@ -88,9 +96,26 @@ func FingerprintEngine(url string, client http.Client) string { } log.Debugf("Response: %s", resp.Body) if imprint.Matcher(resp.Body) { - return engine.Name + addScore(engine.Name, scores) } } } + + log.Debugf("Scores for %s: %v", url, scores) + + // Find the engine with the highest score. + var maxScore int + var maxScoreEngine string + for engineName, score := range scores { + if score > maxScore { + maxScore = score + maxScoreEngine = engineName + } + } + + if maxScore > 0 { + return maxScoreEngine + } + return "unknown" } From 627c504efe0a4491f8cea6229b9bd37e722b297d Mon Sep 17 00:00:00 2001 From: nohehf Date: Fri, 21 Jul 2023 11:37:21 +0200 Subject: [PATCH 5/5] feat: add experimental warning --- cmd/goctopus/goctopus.go | 4 ---- pkg/config/config.go | 12 ++++++++++++ pkg/engine/engine.go | 6 +++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/cmd/goctopus/goctopus.go b/cmd/goctopus/goctopus.go index 9434909..84a82a7 100644 --- a/cmd/goctopus/goctopus.go +++ b/cmd/goctopus/goctopus.go @@ -3,7 +3,6 @@ package main import ( "os" - "github.com/Escape-Technologies/goctopus/internal/utils" "github.com/Escape-Technologies/goctopus/pkg/config" "github.com/Escape-Technologies/goctopus/pkg/goctopus" @@ -12,9 +11,6 @@ import ( func main() { config.LoadFromArgs() - if !config.Get().Silent { - utils.PrintASCII() - } if config.Get().InputFile != "" { input, err := os.Open(config.Get().InputFile) diff --git a/pkg/config/config.go b/pkg/config/config.go index 805510a..3cc7ef0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -87,6 +87,10 @@ func LoadFromArgs() { log.SetLevel(log.ErrorLevel) } + if !config.Silent { + utils.PrintASCII() + } + if err := validateConfig(&config, true); err != nil { log.Error(err) flag.PrintDefaults() @@ -116,6 +120,10 @@ func validateConfig(conf *Config, isCli bool) error { return errors.New("[Invalid config] Please specify an input file or a list of addresses") } + if conf.EngineFingerprinting { + log.Warn("[Experimental] GraphQL engine fingerprinting is enabled. This feature is experimental and may produce false positives. Contributions are welcome to add new engines and test the feature.") + } + return nil } @@ -135,4 +143,8 @@ func Load(config *Config) { if c.Silent { log.SetLevel(log.ErrorLevel) } + + if !config.Silent { + utils.PrintASCII() + } } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 73abdc8..19b4039 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -38,7 +38,11 @@ func inResponseText(matches []string) matcher { func inSection(section string, matches []string) matcher { return func(responseBody *[]byte) bool { var reponseBody map[string]interface{} - json.Unmarshal(*responseBody, &reponseBody) + err := json.Unmarshal(*responseBody, &reponseBody) + if err != nil { + log.Debugf("Error unmarshalling response body: %v", err) + return false + } content, err := json.Marshal(reponseBody[section]) if err != nil { return false