From d85c39ad3a56cf375d3c4356aa4c6ded4776f678 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 22 Dec 2022 12:25:34 +0900 Subject: [PATCH 01/10] Add transformation cache --- internal/corazawaf/rule.go | 36 ++++++++++++++++++++++++++------- internal/corazawaf/rulegroup.go | 11 +++++++++- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go index 24b286c4d..b31c21d85 100644 --- a/internal/corazawaf/rule.go +++ b/internal/corazawaf/rule.go @@ -102,6 +102,8 @@ type Rule struct { // action itself, not sure yet transformations []ruleTransformationParams + transformationsID string + // Slice of initialized actions to be evaluated during // the rule evaluation process actions []ruleActionParams @@ -155,11 +157,11 @@ func (r *Rule) Status() int { // Evaluate will evaluate the current rule for the indicated transaction // If the operator matches, actions will be evaluated, and it will return // the matched variables, keys and values (MatchData) -func (r *Rule) Evaluate(tx rules.TransactionState) []types.MatchData { - return r.doEvaluate(tx.(*Transaction)) +func (r *Rule) Evaluate(tx rules.TransactionState, cache map[transformationKey]string) []types.MatchData { + return r.doEvaluate(tx.(*Transaction), cache) } -func (r *Rule) doEvaluate(tx *Transaction) []types.MatchData { +func (r *Rule) doEvaluate(tx *Transaction, cache map[transformationKey]string) []types.MatchData { if r.Capture { tx.Capture = true } @@ -210,9 +212,27 @@ func (r *Rule) doEvaluate(tx *Transaction) []types.MatchData { // We could try for each until found args, errs = r.executeTransformationsMultimatch(arg.Value()) } else { - ars, es := r.executeTransformations(arg.Value()) - args = []string{ars} - errs = es + if len(r.transformations) == 0 { + args = []string{arg.Value()} + } else if arg.VariableName() == "TX" { + // no cache for TX + ars, es := r.executeTransformations(arg.Value()) + args = []string{ars} + errs = es + } else { + key := transformationKey{ + argKey: arg.Value(), + transformationsID: r.transformationsID, + } + if cached, ok := cache[key]; ok { + args = []string{cached} + } else { + ars, es := r.executeTransformations(arg.Value()) + args = []string{ars} + errs = es + cache[key] = ars + } + } } if len(errs) > 0 { tx.WAF.Logger.Debug("[%s] [%d] Error transforming argument %q for rule %d: %v", tx.id, rid, arg.Value(), r.ID_, errs) @@ -270,7 +290,7 @@ func (r *Rule) doEvaluate(tx *Transaction) []types.MatchData { // we only run the chains for the parent rule for nr := r.Chain; nr != nil; { tx.WAF.Logger.Debug("[%s] [%d] Evaluating rule chain for %d", tx.id, rid, r.ID_) - matchedChainValues := nr.Evaluate(tx) + matchedChainValues := nr.Evaluate(tx, cache) if len(matchedChainValues) == 0 { return matchedChainValues } @@ -382,6 +402,8 @@ func (r *Rule) AddTransformation(name string, t rules.Transformation) error { return fmt.Errorf("invalid transformation %q not found", name) } r.transformations = append(r.transformations, ruleTransformationParams{name, t}) + // TODO: smarter fingerprints than name concatenation + r.transformationsID += name + "|" return nil } diff --git a/internal/corazawaf/rulegroup.go b/internal/corazawaf/rulegroup.go index e1690b74c..f0d97558a 100644 --- a/internal/corazawaf/rulegroup.go +++ b/internal/corazawaf/rulegroup.go @@ -5,6 +5,7 @@ package corazawaf import ( "fmt" + "github.com/corazawaf/coraza/v3/types/variables" "time" "github.com/corazawaf/coraza/v3/internal/strings" @@ -99,6 +100,8 @@ func (rg *RuleGroup) Eval(phase types.RulePhase, tx *Transaction) bool { tx.LastPhase = phase usedRules := 0 ts := time.Now().UnixNano() + // Caches transformations across the rules + transformationCache := map[transformationKey]string{} RulesLoop: for _, r := range tx.WAF.Rules.GetRules() { if tx.interruption != nil && phase != types.PhaseLogging { @@ -136,7 +139,7 @@ RulesLoop: tx.variables.matchedVars.Reset() tx.variables.matchedVarsNames.Reset() - r.Evaluate(tx) + r.Evaluate(tx, transformationCache) tx.Capture = false // we reset captures usedRules++ } @@ -154,3 +157,9 @@ func NewRuleGroup() RuleGroup { rules: []*Rule{}, } } + +type transformationKey struct { + argKey string + argVariable variables.RuleVariable + transformationsID string +} From 732ddfd98815c9ea1fb0783ded4172cc48063041 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 22 Dec 2022 12:28:21 +0900 Subject: [PATCH 02/10] Fix benchmark --- testing/coreruleset/coreruleset_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/coreruleset/coreruleset_test.go b/testing/coreruleset/coreruleset_test.go index a70517e50..eae2b7650 100644 --- a/testing/coreruleset/coreruleset_test.go +++ b/testing/coreruleset/coreruleset_test.go @@ -281,6 +281,7 @@ func crsWAF(t testing.TB) coraza.WAF { t.Fatal(err) } conf := coraza.NewWAFConfig(). + WithRootFS(crsReader). WithDirectives(string(rec)). WithDirectives("Include crs-setup.conf.example"). WithDirectives("Include rules/*.conf") From 6563674a3e39262a6296f42fbfbe0277d6667296 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 22 Dec 2022 12:32:21 +0900 Subject: [PATCH 03/10] Update bench --- testing/coreruleset/coreruleset_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/testing/coreruleset/coreruleset_test.go b/testing/coreruleset/coreruleset_test.go index eae2b7650..478d1ac55 100644 --- a/testing/coreruleset/coreruleset_test.go +++ b/testing/coreruleset/coreruleset_test.go @@ -99,6 +99,7 @@ func BenchmarkCRSSimpleGET(b *testing.B) { func BenchmarkCRSSimplePOST(b *testing.B) { waf := crsWAF(b) + b.ReportAllocs() b.ResetTimer() // only benchmark execution, not compilation for i := 0; i < b.N; i++ { tx := waf.NewTransaction() @@ -280,9 +281,30 @@ func crsWAF(t testing.TB) coraza.WAF { if err != nil { t.Fatal(err) } + customTestingConfig := ` +SecResponseBodyMimeType text/plain +SecDefaultAction "phase:3,log,auditlog,pass" +SecDefaultAction "phase:4,log,auditlog,pass" + +SecAction "id:900005,\ + phase:1,\ + nolog,\ + pass,\ + ctl:ruleEngine=DetectionOnly,\ + ctl:ruleRemoveById=910000,\ + setvar:tx.paranoia_level=4,\ + setvar:tx.crs_validate_utf8_encoding=1,\ + setvar:tx.arg_name_length=100,\ + setvar:tx.arg_length=400,\ + setvar:tx.total_arg_length=64000,\ + setvar:tx.max_num_args=255,\ + setvar:tx.max_file_size=64100,\ + setvar:tx.combined_file_sizes=65535" +` conf := coraza.NewWAFConfig(). WithRootFS(crsReader). WithDirectives(string(rec)). + WithDirectives(customTestingConfig). WithDirectives("Include crs-setup.conf.example"). WithDirectives("Include rules/*.conf") From 789c477a9b0bb5c1f82d10c11d6afa7af054b6a1 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 22 Dec 2022 12:42:47 +0900 Subject: [PATCH 04/10] Pool cache --- internal/corazawaf/rule.go | 2 +- internal/corazawaf/rulegroup.go | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go index b31c21d85..f8f689be9 100644 --- a/internal/corazawaf/rule.go +++ b/internal/corazawaf/rule.go @@ -403,7 +403,7 @@ func (r *Rule) AddTransformation(name string, t rules.Transformation) error { } r.transformations = append(r.transformations, ruleTransformationParams{name, t}) // TODO: smarter fingerprints than name concatenation - r.transformationsID += name + "|" + r.transformationsID += name return nil } diff --git a/internal/corazawaf/rulegroup.go b/internal/corazawaf/rulegroup.go index f0d97558a..706f1a2d1 100644 --- a/internal/corazawaf/rulegroup.go +++ b/internal/corazawaf/rulegroup.go @@ -5,6 +5,7 @@ package corazawaf import ( "fmt" + "github.com/corazawaf/coraza/v3/internal/sync" "github.com/corazawaf/coraza/v3/types/variables" "time" @@ -93,6 +94,10 @@ func (rg *RuleGroup) Clear() { rg.rules = []*Rule{} } +var transformationCachePool = sync.NewPool(func() interface{} { + return map[transformationKey]string{} +}) + // Eval rules for the specified phase, between 1 and 5 // Returns true if transaction is disrupted func (rg *RuleGroup) Eval(phase types.RulePhase, tx *Transaction) bool { @@ -101,7 +106,11 @@ func (rg *RuleGroup) Eval(phase types.RulePhase, tx *Transaction) bool { usedRules := 0 ts := time.Now().UnixNano() // Caches transformations across the rules - transformationCache := map[transformationKey]string{} + transformationCache := transformationCachePool.Get().(map[transformationKey]string) + defer transformationCachePool.Put(transformationCache) + for k := range transformationCache { + delete(transformationCache, k) + } RulesLoop: for _, r := range tx.WAF.Rules.GetRules() { if tx.interruption != nil && phase != types.PhaseLogging { From 7571ad6d73b78d02ad373a3fa8849edb8d797418 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 22 Dec 2022 13:01:57 +0900 Subject: [PATCH 05/10] Finish --- internal/corazawaf/rule.go | 18 ++++++++++++------ internal/corazawaf/rulegroup.go | 15 +++++++-------- internal/corazawaf/transaction.go | 2 ++ internal/corazawaf/waf.go | 1 + 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go index f8f689be9..fff556acd 100644 --- a/internal/corazawaf/rule.go +++ b/internal/corazawaf/rule.go @@ -157,11 +157,11 @@ func (r *Rule) Status() int { // Evaluate will evaluate the current rule for the indicated transaction // If the operator matches, actions will be evaluated, and it will return // the matched variables, keys and values (MatchData) -func (r *Rule) Evaluate(tx rules.TransactionState, cache map[transformationKey]string) []types.MatchData { +func (r *Rule) Evaluate(tx rules.TransactionState, cache map[transformationKey]transformationValue) []types.MatchData { return r.doEvaluate(tx.(*Transaction), cache) } -func (r *Rule) doEvaluate(tx *Transaction, cache map[transformationKey]string) []types.MatchData { +func (r *Rule) doEvaluate(tx *Transaction, cache map[transformationKey]transformationValue) []types.MatchData { if r.Capture { tx.Capture = true } @@ -203,7 +203,7 @@ func (r *Rule) doEvaluate(tx *Transaction, cache map[transformationKey]string) [ values = tx.GetField(v) tx.WAF.Logger.Debug("[%s] [%d] Expanding %d arguments for rule %d", tx.id, rid, len(values), r.ID_) - for _, arg := range values { + for i, arg := range values { var args []string tx.WAF.Logger.Debug("[%s] [%d] Transforming argument %q for rule %d", tx.id, rid, arg.Value(), r.ID_) var errs []error @@ -221,16 +221,22 @@ func (r *Rule) doEvaluate(tx *Transaction, cache map[transformationKey]string) [ errs = es } else { key := transformationKey{ - argKey: arg.Value(), + argKey: arg.Key(), + argIndex: i, + argVariable: arg.Variable(), transformationsID: r.transformationsID, } if cached, ok := cache[key]; ok { - args = []string{cached} + args = cached.args + errs = cached.errs } else { ars, es := r.executeTransformations(arg.Value()) args = []string{ars} errs = es - cache[key] = ars + cache[key] = transformationValue{ + args: args, + errs: es, + } } } } diff --git a/internal/corazawaf/rulegroup.go b/internal/corazawaf/rulegroup.go index 706f1a2d1..9773cf613 100644 --- a/internal/corazawaf/rulegroup.go +++ b/internal/corazawaf/rulegroup.go @@ -5,7 +5,6 @@ package corazawaf import ( "fmt" - "github.com/corazawaf/coraza/v3/internal/sync" "github.com/corazawaf/coraza/v3/types/variables" "time" @@ -94,10 +93,6 @@ func (rg *RuleGroup) Clear() { rg.rules = []*Rule{} } -var transformationCachePool = sync.NewPool(func() interface{} { - return map[transformationKey]string{} -}) - // Eval rules for the specified phase, between 1 and 5 // Returns true if transaction is disrupted func (rg *RuleGroup) Eval(phase types.RulePhase, tx *Transaction) bool { @@ -105,9 +100,7 @@ func (rg *RuleGroup) Eval(phase types.RulePhase, tx *Transaction) bool { tx.LastPhase = phase usedRules := 0 ts := time.Now().UnixNano() - // Caches transformations across the rules - transformationCache := transformationCachePool.Get().(map[transformationKey]string) - defer transformationCachePool.Put(transformationCache) + transformationCache := tx.transformationCache for k := range transformationCache { delete(transformationCache, k) } @@ -169,6 +162,12 @@ func NewRuleGroup() RuleGroup { type transformationKey struct { argKey string + argIndex int argVariable variables.RuleVariable transformationsID string } + +type transformationValue struct { + args []string + errs []error +} diff --git a/internal/corazawaf/transaction.go b/internal/corazawaf/transaction.go index 82c5a6f01..a9e6b3df2 100644 --- a/internal/corazawaf/transaction.go +++ b/internal/corazawaf/transaction.go @@ -104,6 +104,8 @@ type Transaction struct { audit bool variables TransactionVariables + + transformationCache map[transformationKey]transformationValue } func (tx *Transaction) ID() string { diff --git a/internal/corazawaf/waf.go b/internal/corazawaf/waf.go index abc24b328..07d3d5dd3 100644 --- a/internal/corazawaf/waf.go +++ b/internal/corazawaf/waf.go @@ -190,6 +190,7 @@ func (w *WAF) newTransactionWithID(id string) *Transaction { MemoryLimit: w.RequestBodyInMemoryLimit, }) tx.variables = *NewTransactionVariables() + tx.transformationCache = map[transformationKey]transformationValue{} } // set capture variables From a6566b26d0bef22f728312d5f12613a47ff07f94 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 22 Dec 2022 14:08:48 +0900 Subject: [PATCH 06/10] Fix benchmark and add large version --- testing/coreruleset/coreruleset_test.go | 36 ++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/testing/coreruleset/coreruleset_test.go b/testing/coreruleset/coreruleset_test.go index 478d1ac55..e3331844a 100644 --- a/testing/coreruleset/coreruleset_test.go +++ b/testing/coreruleset/coreruleset_test.go @@ -110,7 +110,41 @@ func BenchmarkCRSSimplePOST(b *testing.B) { tx.AddRequestHeader("Accept", "application/json") tx.AddRequestHeader("Content-Type", "application/x-www-form-urlencoded") tx.ProcessRequestHeaders() - if _, err := tx.ResponseBodyWriter().Write([]byte("parameters2=and&other2=Stuff")); err != nil { + if _, err := tx.RequestBodyWriter().Write([]byte("parameters2=and&other2=Stuff")); err != nil { + b.Error(err) + } + if _, err := tx.ProcessRequestBody(); err != nil { + b.Error(err) + } + tx.AddResponseHeader("Content-Type", "application/json") + tx.ProcessResponseHeaders(200, "OK") + if _, err := tx.ProcessResponseBody(); err != nil { + b.Error(err) + } + tx.ProcessLogging() + if err := tx.Close(); err != nil { + b.Error(err) + } + } +} + +func BenchmarkCRSLargePOST(b *testing.B) { + waf := crsWAF(b) + + postPayload := []byte(fmt.Sprintf("parameters2=and&other2=%s", strings.Repeat("a", 10000))) + + b.ReportAllocs() + b.ResetTimer() // only benchmark execution, not compilation + for i := 0; i < b.N; i++ { + tx := waf.NewTransaction() + tx.ProcessConnection("127.0.0.1", 8080, "127.0.0.1", 8080) + tx.ProcessURI("POST", "/some_path/with?parameters=and&other=Stuff", "HTTP/1.1") + tx.AddRequestHeader("Host", "localhost") + tx.AddRequestHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36") + tx.AddRequestHeader("Accept", "application/json") + tx.AddRequestHeader("Content-Type", "application/x-www-form-urlencoded") + tx.ProcessRequestHeaders() + if _, err := tx.RequestBodyWriter().Write(postPayload); err != nil { b.Error(err) } if _, err := tx.ProcessRequestBody(); err != nil { From 5585154af280e8717462cbfcbd05127e1d1d93b4 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 22 Dec 2022 14:11:18 +0900 Subject: [PATCH 07/10] Format --- config_test.go | 3 +++ internal/corazawaf/rulegroup.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config_test.go b/config_test.go index 81ced5e07..cc7fe4409 100644 --- a/config_test.go +++ b/config_test.go @@ -1,3 +1,6 @@ +// Copyright 2022 Juan Pablo Tosso and the OWASP Coraza contributors +// SPDX-License-Identifier: Apache-2.0 + package coraza import ( diff --git a/internal/corazawaf/rulegroup.go b/internal/corazawaf/rulegroup.go index 9773cf613..aa0eaa20a 100644 --- a/internal/corazawaf/rulegroup.go +++ b/internal/corazawaf/rulegroup.go @@ -5,11 +5,11 @@ package corazawaf import ( "fmt" - "github.com/corazawaf/coraza/v3/types/variables" "time" "github.com/corazawaf/coraza/v3/internal/strings" "github.com/corazawaf/coraza/v3/types" + "github.com/corazawaf/coraza/v3/types/variables" ) // RuleGroup is a collection of rules From fc1554f395f331034af6f15250f3ff125583f736 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 22 Dec 2022 14:18:40 +0900 Subject: [PATCH 08/10] Lint --- internal/corazawaf/rule.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go index fff556acd..0bfff0c07 100644 --- a/internal/corazawaf/rule.go +++ b/internal/corazawaf/rule.go @@ -212,14 +212,15 @@ func (r *Rule) doEvaluate(tx *Transaction, cache map[transformationKey]transform // We could try for each until found args, errs = r.executeTransformationsMultimatch(arg.Value()) } else { - if len(r.transformations) == 0 { + switch { + case len(r.transformations) == 0: args = []string{arg.Value()} - } else if arg.VariableName() == "TX" { + case arg.VariableName() == "TX": // no cache for TX ars, es := r.executeTransformations(arg.Value()) args = []string{ars} errs = es - } else { + default: key := transformationKey{ argKey: arg.Key(), argIndex: i, From 8c4f48fd1541b3aea384ed028c7818a8954ee7cb Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Thu, 22 Dec 2022 16:10:19 +0900 Subject: [PATCH 09/10] memoize transformation id --- internal/corazawaf/rule.go | 27 ++++++++++++++++++++++++--- internal/corazawaf/rulegroup.go | 2 +- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go index 0bfff0c07..aa1d3394f 100644 --- a/internal/corazawaf/rule.go +++ b/internal/corazawaf/rule.go @@ -8,6 +8,7 @@ import ( "regexp" "strconv" "strings" + "sync" "github.com/corazawaf/coraza/v3/internal/corazarules" "github.com/corazawaf/coraza/v3/macro" @@ -102,7 +103,7 @@ type Rule struct { // action itself, not sure yet transformations []ruleTransformationParams - transformationsID string + transformationsID int // Slice of initialized actions to be evaluated during // the rule evaluation process @@ -402,6 +403,27 @@ func (r *Rule) AddVariableNegation(v variables.RuleVariable, key string) error { return nil } +var transformationIDToName = []string{""} +var transformationNameToID = map[string]int{"": 0} +var transformationIDsLock = sync.Mutex{} + +func transformationID(currentID int, transformationName string) int { + transformationIDsLock.Lock() + defer transformationIDsLock.Unlock() + + currName := transformationIDToName[currentID] + nextName := fmt.Sprintf("%s+%s", currName, transformationName) + if id, ok := transformationNameToID[nextName]; ok { + return id + } + + id := len(transformationIDToName) + transformationIDToName = append(transformationIDToName, nextName) + transformationIDToName[id] = nextName + transformationNameToID[nextName] = id + return id +} + // AddTransformation adds a transformation to the rule // it fails if the transformation cannot be found func (r *Rule) AddTransformation(name string, t rules.Transformation) error { @@ -409,8 +431,7 @@ func (r *Rule) AddTransformation(name string, t rules.Transformation) error { return fmt.Errorf("invalid transformation %q not found", name) } r.transformations = append(r.transformations, ruleTransformationParams{name, t}) - // TODO: smarter fingerprints than name concatenation - r.transformationsID += name + r.transformationsID = transformationID(r.transformationsID, name) return nil } diff --git a/internal/corazawaf/rulegroup.go b/internal/corazawaf/rulegroup.go index aa0eaa20a..885574999 100644 --- a/internal/corazawaf/rulegroup.go +++ b/internal/corazawaf/rulegroup.go @@ -164,7 +164,7 @@ type transformationKey struct { argKey string argIndex int argVariable variables.RuleVariable - transformationsID string + transformationsID int } type transformationValue struct { From 4b698a07bc615be361b77c0963dcbce6729f323d Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 23 Dec 2022 09:26:46 +0900 Subject: [PATCH 10/10] Cleanup --- internal/corazawaf/rule.go | 74 +++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go index aa1d3394f..f12aa38b1 100644 --- a/internal/corazawaf/rule.go +++ b/internal/corazawaf/rule.go @@ -205,43 +205,8 @@ func (r *Rule) doEvaluate(tx *Transaction, cache map[transformationKey]transform values = tx.GetField(v) tx.WAF.Logger.Debug("[%s] [%d] Expanding %d arguments for rule %d", tx.id, rid, len(values), r.ID_) for i, arg := range values { - var args []string tx.WAF.Logger.Debug("[%s] [%d] Transforming argument %q for rule %d", tx.id, rid, arg.Value(), r.ID_) - var errs []error - if r.MultiMatch { - // TODO in the future, we don't need to run every transformation - // We could try for each until found - args, errs = r.executeTransformationsMultimatch(arg.Value()) - } else { - switch { - case len(r.transformations) == 0: - args = []string{arg.Value()} - case arg.VariableName() == "TX": - // no cache for TX - ars, es := r.executeTransformations(arg.Value()) - args = []string{ars} - errs = es - default: - key := transformationKey{ - argKey: arg.Key(), - argIndex: i, - argVariable: arg.Variable(), - transformationsID: r.transformationsID, - } - if cached, ok := cache[key]; ok { - args = cached.args - errs = cached.errs - } else { - ars, es := r.executeTransformations(arg.Value()) - args = []string{ars} - errs = es - cache[key] = transformationValue{ - args: args, - errs: es, - } - } - } - } + args, errs := r.transformArg(arg, i, cache) if len(errs) > 0 { tx.WAF.Logger.Debug("[%s] [%d] Error transforming argument %q for rule %d: %v", tx.id, rid, arg.Value(), r.ID_, errs) } @@ -324,6 +289,42 @@ func (r *Rule) doEvaluate(tx *Transaction, cache map[transformationKey]transform return matchedValues } +func (r *Rule) transformArg(arg types.MatchData, argIdx int, cache map[transformationKey]transformationValue) ([]string, []error) { + if r.MultiMatch { + // TODO in the future, we don't need to run every transformation + // We could try for each until found + return r.executeTransformationsMultimatch(arg.Value()) + } else { + switch { + case len(r.transformations) == 0: + return []string{arg.Value()}, nil + case arg.VariableName() == "TX": + // no cache for TX + arg, errs := r.executeTransformations(arg.Value()) + return []string{arg}, errs + default: + key := transformationKey{ + argKey: arg.Key(), + argIndex: argIdx, + argVariable: arg.Variable(), + transformationsID: r.transformationsID, + } + if cached, ok := cache[key]; ok { + return cached.args, cached.errs + } else { + ars, es := r.executeTransformations(arg.Value()) + args := []string{ars} + errs := es + cache[key] = transformationValue{ + args: args, + errs: es, + } + return args, errs + } + } + } +} + func (r *Rule) matchVariable(tx *Transaction, m *corazarules.MatchData) { rid := r.ID_ if rid == 0 { @@ -419,7 +420,6 @@ func transformationID(currentID int, transformationName string) int { id := len(transformationIDToName) transformationIDToName = append(transformationIDToName, nextName) - transformationIDToName[id] = nextName transformationNameToID[nextName] = id return id }