diff --git a/pkg/acquisition/modules/waap/waap.go b/pkg/acquisition/modules/waap/waap.go index 5e7c3e632..d6859fddb 100644 --- a/pkg/acquisition/modules/waap/waap.go +++ b/pkg/acquisition/modules/waap/waap.go @@ -149,6 +149,12 @@ func (w *WaapSource) Configure(yamlConfig []byte, logger *log.Entry) error { return fmt.Errorf("no waap_config provided") } + err = w.WaapRuntime.ProcessOnLoadRules() + + if err != nil { + return fmt.Errorf("unable to process on load rules : %s", err) + } + w.WaapRunners = make([]WaapRunner, w.config.Routines) for nbRoutine := 0; nbRoutine < w.config.Routines; nbRoutine++ { diff --git a/pkg/waf/waap.go b/pkg/waf/waap.go index 4100c760f..0bafff1a8 100644 --- a/pkg/waf/waap.go +++ b/pkg/waf/waap.go @@ -186,6 +186,35 @@ func (wc *WaapConfig) Build() (*WaapRuntimeConfig, error) { return ret, nil } +func (w *WaapRuntimeConfig) ProcessOnLoadRules() error { + for _, rule := range w.CompiledOnMatch { + if rule.FilterExpr != nil { + output, err := expr.Run(rule.FilterExpr, GetHookEnv(w, ParsedRequest{})) + if err != nil { + return fmt.Errorf("unable to run filter %s : %w", rule.Filter, err) + } + switch t := output.(type) { + case bool: + if !t { + log.Infof("filter didnt match") + continue + } + default: + log.Errorf("Filter must return a boolean, can't filter") + continue + } + } + for _, applyExpr := range rule.ApplyExpr { + _, err := expr.Run(applyExpr, GetHookEnv(w, ParsedRequest{})) + if err != nil { + log.Errorf("unable to apply filter: %s", err) + continue + } + } + } + return nil +} + func (w *WaapRuntimeConfig) ProcessOnMatchRules(request ParsedRequest) error { for _, rule := range w.CompiledOnMatch { diff --git a/pkg/waf/waap_rule.go b/pkg/waf/waap_rule.go index 5cc934a45..eabee5866 100644 --- a/pkg/waf/waap_rule.go +++ b/pkg/waf/waap_rule.go @@ -3,6 +3,7 @@ package waf import ( "fmt" "strings" + "time" ) type VPatchRule struct { @@ -17,8 +18,6 @@ type VPatchRule struct { Detect string `yaml:"detect"` //@detectXSS, @detectSQLi, etc Logic string `yaml:"logic,omitempty"` // "AND", "OR", or empty if not applicable SubRules []VPatchRule `yaml:"sub_rules,omitempty"` - - id int } func (v *VPatchRule) String() string { @@ -46,26 +45,21 @@ func (v *VPatchRule) constructRule(depth int) string { switch v.Logic { case "AND": - // Add "chain" to the current rule result = strings.TrimSuffix(result, `"`) + `,chain"` + "\n" for _, subRule := range v.SubRules { result += subRule.constructRule(depth + 1) } case "OR": skips := countTotalRules(v.SubRules) - 1 - // If the "OR" rule is at the top level and is followed by any rule, we need to count that too if depth == 0 { - skips++ // For the current rule + skips++ } - // Add the skip directive to the current rule too result = strings.TrimSuffix(result, `"`) + fmt.Sprintf(`,skip:%d"`+"\n", skips) for _, subRule := range v.SubRules { skips-- if skips > 0 { - // Append skip directive and decrease the skip count result += strings.TrimSuffix(subRule.singleRuleString(), `"`) + fmt.Sprintf(`,skip:%d"`+"\n", skips) } else { - // If no skip is required, append only a newline result += subRule.singleRuleString() + "\n" } } @@ -91,7 +85,9 @@ func (v *VPatchRule) singleRuleString() string { ruleStr = fmt.Sprintf(`SecRule %s "%s"`, v.Target, operator) } - actions := fmt.Sprintf(` "id:%d,deny,log`, v.id) + //FIXME: phase2 should probably not be hardcoded + //Find a better way than using time.Now().UnixMilli() to generate a unique ID + actions := fmt.Sprintf(` "id:%d,deny,log,phase:2`, time.Now().UnixNano()) // Handle transformation if v.Transform != "" { diff --git a/pkg/waf/waap_rule_test.go b/pkg/waf/waap_rule_test.go index 59cc7f897..fcb45a2d3 100644 --- a/pkg/waf/waap_rule_test.go +++ b/pkg/waf/waap_rule_test.go @@ -33,6 +33,25 @@ func TestVPatchRuleString(t *testing.T) { }, }, expected: `SecRule ARGS:bar "@rx [0-9]" "id:0,deny,log,chain" +SecRule REQUEST_URI "@rx /joomla/index.php/component/users/" "id:0,deny,log"`, + }, + { + name: "AND Logic Rule", + rule: VPatchRule{ + Logic: "AND", + SubRules: []VPatchRule{ + { + Target: "REQUEST_URI", + Match: "/joomla/index.php/component/users/", + }, + { + Target: "ARGS", + Variable: "bar", + Match: "[0-9]", + }, + }, + }, + expected: `SecRule ARGS:bar "@rx [0-9]" "id:0,deny,log,chain" SecRule REQUEST_URI "@rx /joomla/index.php/component/users/" "id:0,deny,log"`, }, { diff --git a/pkg/waf/waap_rules_collection.go b/pkg/waf/waap_rules_collection.go index 0e3cc9edd..2ee094a0c 100644 --- a/pkg/waf/waap_rules_collection.go +++ b/pkg/waf/waap_rules_collection.go @@ -27,6 +27,7 @@ type WaapCollectionConfig struct { SecLangFilesRules []string `yaml:"seclang_files_rules"` SecLangRules []string `yaml:"seclang_rules"` Rules []VPatchRule `yaml:"rules"` + Data interface{} `yaml:"data"` //Ignore it } func LoadCollection(collection string) (WaapCollection, error) {