From a86873b9268132ff96f3cdb9bea1616cfe4490b0 Mon Sep 17 00:00:00 2001 From: Juan Pablo Tosso Date: Sat, 9 Jul 2022 12:43:50 -0400 Subject: [PATCH] feat(operator): New RESTPATH operator support (#282) --- operators/restpath.go | 66 ++++++++++++++++++++++++++++++++++++ operators/restpath_test.go | 41 ++++++++++++++++++++++ transaction.go | 1 + types/variables/variables.go | 3 ++ waf.go | 4 +++ 5 files changed, 115 insertions(+) create mode 100644 operators/restpath.go create mode 100644 operators/restpath_test.go diff --git a/operators/restpath.go b/operators/restpath.go new file mode 100644 index 000000000..3de2382dc --- /dev/null +++ b/operators/restpath.go @@ -0,0 +1,66 @@ +// Copyright 2022 Juan Pablo Tosso +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package operators + +import ( + "fmt" + "regexp" + "strings" + + "github.com/corazawaf/coraza/v3" +) + +var rePathTokenRe = regexp.MustCompile(`\{([^\}]+)\}`) + +// @restpath takes as argument a path expression in the format +// /path/to/resource/{id}/{name}/{age} +// It will later transform the path to a regex and assign the variables to +// ARGS_PATH +type restpath struct { + re *regexp.Regexp +} + +func (o *restpath) Init(options coraza.RuleOperatorOptions) error { + data := strings.ReplaceAll(options.Arguments, "/", "\\/") + for _, token := range rePathTokenRe.FindAllStringSubmatch(data, -1) { + data = strings.Replace(data, token[0], fmt.Sprintf("(?P<%s>.*)", token[1]), 1) + } + re, err := regexp.Compile(data) + o.re = re + return err +} + +func (o *restpath) Evaluate(tx *coraza.Transaction, value string) bool { + // we use the re regex to match the path and match named captured groups + // to the ARGS_PATH + match := o.re.FindStringSubmatch(value) + if len(match) == 0 { + return false + } + for i, m := range o.re.SubexpNames() { + if i != 0 && m != "" { + tx.Variables.ArgsPath.SetIndex(m, 0, match[i]) + } + } + return true +} + +var _ coraza.RuleOperator = &restpath{} + +func init() { + Register("restpath", func() coraza.RuleOperator { + return &restpath{} + }) +} diff --git a/operators/restpath_test.go b/operators/restpath_test.go new file mode 100644 index 000000000..fca91a652 --- /dev/null +++ b/operators/restpath_test.go @@ -0,0 +1,41 @@ +// Copyright 2022 Juan Pablo Tosso +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package operators + +import ( + "context" + "testing" + + "github.com/corazawaf/coraza/v3" +) + +func TestRestPath(t *testing.T) { + waf := coraza.NewWaf() + tx := waf.NewTransaction(context.Background()) + exp := "/some-random/url-{id}/{name}" + path := "/some-random/url-123/juan" + rp := restpath{} + if err := rp.Init(coraza.RuleOperatorOptions{ + Arguments: exp, + }); err != nil { + t.Error(err) + } + if !rp.Evaluate(tx, path) { + t.Errorf("Expected %s to match %s", exp, path) + } + if tx.Variables.ArgsPath.Get("id")[0] != "123" { + t.Errorf("Expected 123, got %s", tx.Variables.ArgsPath.Get("id")) + } +} diff --git a/transaction.go b/transaction.go index ba83df11b..9f222f450 100644 --- a/transaction.go +++ b/transaction.go @@ -974,6 +974,7 @@ type TransactionVariables struct { // Maps Variables ArgsGet *collection.CollectionMap ArgsPost *collection.CollectionMap + ArgsPath *collection.CollectionMap FilesTmpNames *collection.CollectionMap Geo *collection.CollectionMap Files *collection.CollectionMap diff --git a/types/variables/variables.go b/types/variables/variables.go index 74936c32d..2681ac7fe 100644 --- a/types/variables/variables.go +++ b/types/variables/variables.go @@ -172,6 +172,8 @@ const ( ArgsGet // ArgsPost contains the POST (BODY) arguments ArgsPost + // ArgsPath contains the url path parts + ArgsPath // FilesSizes contains the sizes of the uploaded files FilesSizes // FilesNames contains the names of the uploaded files @@ -296,6 +298,7 @@ var rulemap = map[RuleVariable]string{ Args: "ARGS", ArgsGet: "ARGS_GET", ArgsPost: "ARGS_POST", + ArgsPath: "ARGS_PATH", FilesSizes: "FILES_SIZES", FilesNames: "FILES_NAMES", FilesTmpContent: "FILES_TMP_CONTENT", diff --git a/waf.go b/waf.go index 9ffe834aa..c2504ce55 100644 --- a/waf.go +++ b/waf.go @@ -312,6 +312,8 @@ func (w *Waf) NewTransaction(ctx context.Context) *Transaction { tx.Collections[variables.ArgsGet] = tx.Variables.ArgsGet tx.Variables.ArgsPost = collection.NewCollectionMap(variables.ArgsPost) tx.Collections[variables.ArgsPost] = tx.Variables.ArgsPost + tx.Variables.ArgsPath = collection.NewCollectionMap(variables.ArgsPath) + tx.Collections[variables.ArgsPath] = tx.Variables.ArgsPath tx.Variables.FilesSizes = collection.NewCollectionMap(variables.FilesSizes) tx.Collections[variables.FilesSizes] = tx.Variables.FilesSizes tx.Variables.FilesTmpContent = collection.NewCollectionMap(variables.FilesTmpContent) @@ -352,6 +354,7 @@ func (w *Waf) NewTransaction(ctx context.Context) *Transaction { variables.Args, tx.Variables.ArgsGet, tx.Variables.ArgsPost, + tx.Variables.ArgsPath, ) tx.Collections[variables.Args] = tx.Variables.Args @@ -359,6 +362,7 @@ func (w *Waf) NewTransaction(ctx context.Context) *Transaction { variables.ArgsNames, tx.Variables.ArgsGet, tx.Variables.ArgsPost, + tx.Variables.ArgsPath, ) tx.Collections[variables.ArgsNames] = tx.Variables.ArgsNames tx.Variables.ArgsGetNames = collection.NewCollectionTranslationProxy(