diff --git a/pkg/appsec/appsec_rule/modsec_rule_test.go b/pkg/appsec/appsec_rule/modsec_rule_test.go index 27eedd7708d..c803e635654 100644 --- a/pkg/appsec/appsec_rule/modsec_rule_test.go +++ b/pkg/appsec/appsec_rule/modsec_rule_test.go @@ -2,6 +2,9 @@ package appsec_rule import ( "testing" + + "github.com/corazawaf/coraza/v3" + "github.com/stretchr/testify/require" ) func TestVPatchRuleString(t *testing.T) { @@ -10,6 +13,7 @@ func TestVPatchRuleString(t *testing.T) { description string rule CustomRule expected string + invalid bool }{ { name: "Collection count", @@ -150,6 +154,7 @@ SecRule ARGS_GET:bar "@rx [^a-zA-Z]" "id:271441587,phase:2,deny,log,msg:'test ru { name: "OR AND mix", description: "test rule", + invalid: true, rule: CustomRule{ And: []CustomRule{ @@ -179,6 +184,15 @@ SecRule ARGS_GET:bar "@rx [^a-zA-Z]" "id:271441587,phase:2,deny,log,msg:'test ru SecRule ARGS_GET:bar "@rx [^a-zA-Z]" "id:1519945803,phase:2,deny,log,msg:'test rule',tag:'crowdsec-OR AND mix',tag:'cs-custom-rule',t:lowercase" SecRule ARGS_GET:foo "@rx [^a-zA-Z]" "id:1519945803,phase:2,deny,log,msg:'test rule',tag:'crowdsec-OR AND mix',tag:'cs-custom-rule',severity:'emergency',t:lowercase"`, }, + { + name: "all transforms", + rule: CustomRule{ + Zones: []string{"ARGS"}, + Variables: []string{"foo"}, + Match: Match{Type: "regex", Value: "[^a-zA-Z]"}, + Transform: []string{"lowercase", "uppercase", "length", "trim", "trim_left", "trim_right", "htmlentitydecode", "js_decode", "css_decode", "urldecode", "hexdecode", "cmdline", "b64decode", "b64decode_lenient", "b64encode", "normalize_path", "normalize_path_win", "remove_whitespaces", "compress_whitespaces", "remove_nulls", "replace_nulls", "remove_comments", "replace_comments"}, + }, + }, } for _, tt := range tests { @@ -187,9 +201,17 @@ SecRule ARGS_GET:foo "@rx [^a-zA-Z]" "id:1519945803,phase:2,deny,log,msg:'test r if err != nil { t.Errorf("Error converting rule: %s", err) } - if actual != tt.expected { + if tt.expected != "" && actual != tt.expected { t.Errorf("Expected:\n%s\nGot:\n%s", tt.expected, actual) } + // Attempt to parse the rule to make sure we generated a valid one + cfg := coraza.NewWAFConfig().WithDirectives(actual) + _, err = coraza.NewWAF(cfg) + if tt.invalid { + require.Error(t, err) + } else { + require.NoError(t, err) + } }) } } diff --git a/pkg/appsec/appsec_rule/modsecurity.go b/pkg/appsec/appsec_rule/modsecurity.go index 65583d54d9a..b1cffc1f8c9 100644 --- a/pkg/appsec/appsec_rule/modsecurity.go +++ b/pkg/appsec/appsec_rule/modsecurity.go @@ -36,15 +36,36 @@ var zonesMap = map[string]string{ var transformMap = map[string]string{ "lowercase": "t:lowercase", "uppercase": "t:uppercase", - "b64decode": "t:base64Decode", - //"hexdecode": "t:hexDecode", -> not supported by coraza - "length": "t:length", + "length": "t:length", + // trim + "trim": "t:trim", + "trim_left": "t:trimLeft", + "trim_right": "t:trimRight", + // Decoding transforms + "htmlentitydecode": "t:htmlEntityDecode", + "html_entity_decode": "t:htmlEntityDecode", + "js_decode": "t:jsDecode", + "css_decode": "t:cssDecode", "urldecode": "t:urlDecode", - "trim": "t:trim", + "hexdecode": "t:hexDecode", + "cmdline": "t:cmdLine", + // b64-related transforms + "b64decode": "t:base64Decode", // strict base64 decode + "b64decode_lenient": "t:base64DecodeExt", // lenient base64 decode (no padding, decode up to first invalid char, skip whitespaces and dots) + "b64encode": "t:base64Encode", + // Path normalization "normalize_path": "t:normalizePath", "normalizepath": "t:normalizePath", - "htmlentitydecode": "t:htmlEntityDecode", - "html_entity_decode": "t:htmlEntityDecode", + "normalize_path_win": "t:normalizePathWin", + "normalizepathwin": "t:normalizePathWin", + // Whitespaces + "remove_whitespaces": "t:removeWhitespace", + "compress_whitespaces": "t:compressWhitespace", + "remove_nulls": "t:removeNulls", + "replace_nulls": "t:replaceNulls", + // Comments + "remove_comments": "t:removeComments", + "replace_comments": "t:replaceComments", } var matchMap = map[string]string{