Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ require (
github.com/corazawaf/coraza-coreruleset v0.0.0-20240226094324-415b1017abdc
github.com/corazawaf/libinjection-go v0.2.2
github.com/foxcpp/go-mockdns v1.1.0
github.com/graphql-go/graphql v0.8.1
github.com/jcchavezs/mergefs v0.1.0
github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516
github.com/mccutchen/go-httpbin/v2 v2.15.0
github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.18.0
github.com/valllabh/ocsf-schema-golang v1.0.3
golang.org/x/net v0.34.0
Expand All @@ -36,13 +38,13 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/miekg/dns v1.1.57 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/tools v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

retract v3.2.2
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7Dlme
github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
github.com/jcchavezs/mergefs v0.1.0 h1:7oteO7Ocl/fnfFMkoVLJxTveCjrsd//UB0j89xmnpec=
github.com/jcchavezs/mergefs v0.1.0/go.mod h1:eRLTrsA+vFwQZ48hj8p8gki/5v9C2bFtHH5Mnn4bcGk=
github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516 h1:aAO0L0ulox6m/CLRYvJff+jWXYYCKGpEm3os7dM/Z+M=
Expand Down Expand Up @@ -53,8 +55,6 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -75,8 +75,6 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down Expand Up @@ -104,6 +102,8 @@ golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
Expand Down
3 changes: 3 additions & 0 deletions internal/bodyprocessors/bodyprocessor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package bodyprocessors

var ReadJson = readJSON
2 changes: 1 addition & 1 deletion internal/bodyprocessors/bodyprocessors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var processors = map[string]bodyProcessorWrapper{}
// by name. If the body processor is already registered,
// it will be overwritten
func RegisterBodyProcessor(name string, fn func() plugintypes.BodyProcessor) {
processors[name] = fn
processors[strings.ToLower(name)] = fn
}

// GetBodyProcessor returns a body processor by name
Expand Down
215 changes: 215 additions & 0 deletions internal/bodyprocessors/graphql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// Copyright 2022 Juan Pablo Tosso and the OWASP Coraza contributors
// SPDX-License-Identifier: Apache-2.0

package bodyprocessors

import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"

"github.com/corazawaf/coraza/v3/collection"
"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
"github.com/graphql-go/graphql/language/ast"
"github.com/graphql-go/graphql/language/parser"
"github.com/tidwall/gjson"
)

type graphqlBodyProcessor struct{}

type graphqlRequest struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables"`
OperationName string `json:"operationName"`
}

var _ plugintypes.BodyProcessor = &graphqlBodyProcessor{}

func (gp *graphqlBodyProcessor) ProcessRequest(reader io.Reader, v plugintypes.TransactionVariables, _ plugintypes.BodyProcessorOptions) error {
bodyBytes, err := io.ReadAll(reader)
if err != nil {
return fmt.Errorf("failed to read request body: %w", err)
}
bodyStr := strings.TrimSpace(string(bodyBytes))

// Check for batch gql requests
if strings.HasPrefix(bodyStr, "[") {
if err := processBatchGraphQL(bodyStr, v); err == nil {
return nil
}
}

// Process single gql request
if err := processSingleGraphQL(bodyStr, v); err == nil {
return nil
}

// Fallback to processing as JsON
return processJSONRequest(bodyStr, v)
}

func processBatchGraphQL(bodyStr string, v plugintypes.TransactionVariables) error {
var requests []graphqlRequest
if err := json.Unmarshal([]byte(bodyStr), &requests); err != nil {
return fmt.Errorf("invalid gql batch request: %w", err)
}

for _, req := range requests {
if req.Query == "" {
continue
}
if err := processGraphQLRequest(req.Query, v); err != nil {
return fmt.Errorf("invalid gql batch request: %w", err)
}
processGraphQLVariables(req.Variables, v)
}

return nil
}

func processSingleGraphQL(bodyStr string, v plugintypes.TransactionVariables) error {
var req graphqlRequest
if err := json.Unmarshal([]byte(bodyStr), &req); err != nil {
return fmt.Errorf("invalid gql request: %w", err)
}

if req.Query == "" {
return fmt.Errorf("missing gql query")
}

if err := processGraphQLRequest(req.Query, v); err != nil {
return fmt.Errorf("invalid gql request: %w", err)
}

processGraphQLVariables(req.Variables, v)
return nil
}

func processJSONRequest(bodyStr string, v plugintypes.TransactionVariables) error {
items, err := parseJSON(bodyStr)
if err != nil {
return err
}
col := v.ArgsPost()
for k, v := range items {
col.SetIndex(k, 0, v)
}
return nil
}

func processGraphQLVariables(variables map[string]interface{}, v plugintypes.TransactionVariables) {
if len(variables) == 0 {
return
}

col := v.ArgsPost()
for k, val := range variables {
switch v := val.(type) {
case string:
col.Add(k, v)
default:
jsonStr, _ := json.Marshal(v)
col.Add(k, string(jsonStr))
}
}
}

func (gp *graphqlBodyProcessor) ProcessResponse(reader io.Reader, v plugintypes.TransactionVariables, _ plugintypes.BodyProcessorOptions) error {
col := v.ResponseArgs()
data, err := readJSON(reader)
if err != nil {
return err
}
for key, value := range data {
col.SetIndex(key, 0, value)
}
return nil
}

func processGraphQLRequest(query string, v plugintypes.TransactionVariables) error {
doc, err := parser.Parse(parser.ParseParams{Source: query})
if err != nil {
return fmt.Errorf("invalid GraphQL query: %w", err)
}

headersCol := v.RequestHeaders()
argsCol := v.ArgsPost()

for _, def := range doc.Definitions {
if opDef, ok := def.(*ast.OperationDefinition); ok {
operationName := opDef.Operation
if opDef.Name != nil {
operationName += opDef.Name.Value
}
headersCol.SetIndex("graphql.operation", 0, operationName)

if opDef.SelectionSet != nil {
for _, selection := range opDef.SelectionSet.Selections {
if field, ok := selection.(*ast.Field); ok {
headersCol.SetIndex("graphql.field", 0, field.Name.Value)
processGraphQLArguments(field.Arguments, argsCol)
}
}
}
}
}
return nil
}

func processGraphQLArguments(args []*ast.Argument, col collection.Map) {
for _, arg := range args {
valMap := flattenArgument(arg)
for k, v := range valMap {
col.Add(k, v)
}
}
}

func parseJSON(s string) (map[string]string, error) {
jsonParsed := gjson.Parse(s)
result := make(map[string]string)
key := []byte("")
readItems(jsonParsed, key, result)
return result, nil
}

func flattenArgument(arg *ast.Argument) map[string]string {
res := make(map[string]string)
key := []byte(arg.Name.Value)
readAstValue(key, arg.Value, res)
return res
}

func readAstValue(objKey []byte, value ast.Value, res map[string]string) {
switch v := value.(type) {
case *ast.StringValue:
res[string(objKey)] = v.Value
case *ast.IntValue:
res[string(objKey)] = v.Value
case *ast.FloatValue:
res[string(objKey)] = v.Value
case *ast.BooleanValue:
res[string(objKey)] = strconv.FormatBool(v.Value)
case *ast.EnumValue:
res[string(objKey)] = v.Value
case *ast.ListValue:
for i, val := range v.Values {
subKey := append(objKey, fmt.Sprintf(".[%d]", i)...)
readAstValue(subKey, val, res)
}
res[string(objKey)] = strconv.Itoa(len(v.Values))
case *ast.ObjectValue:
for _, field := range v.Fields {
subKey := append(objKey, "."+field.Name.Value...)
readAstValue(subKey, field.Value, res)
}
}
}

func init() {
RegisterBodyProcessor("graphql", func() plugintypes.BodyProcessor {
return &graphqlBodyProcessor{}
})
}
Loading
Loading