diff --git a/pkg/acquisition/modules/appsec/appsec.go b/pkg/acquisition/modules/appsec/appsec.go index 49830eb852f..def6a6886ec 100644 --- a/pkg/acquisition/modules/appsec/appsec.go +++ b/pkg/acquisition/modules/appsec/appsec.go @@ -335,7 +335,7 @@ func (w *AppsecSource) appsecHandler(rw http.ResponseWriter, r *http.Request) { } // parse the request only once - parsedRequest, err := appsec.NewParsedRequestFromRequest(r) + parsedRequest, err := appsec.NewParsedRequestFromRequest(r, w.logger) if err != nil { w.logger.Errorf("%s", err) rw.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/appsec/appsec_rule/appsec_rule.go b/pkg/appsec/appsec_rule/appsec_rule.go index c011e58fb02..4bc46ef505b 100644 --- a/pkg/appsec/appsec_rule/appsec_rule.go +++ b/pkg/appsec/appsec_rule/appsec_rule.go @@ -28,6 +28,7 @@ rules: type match struct { Type string `yaml:"type"` Value string `yaml:"value"` + Not bool `yaml:"not,omitempty"` } type CustomRule struct { @@ -40,7 +41,8 @@ type CustomRule struct { Transform []string `yaml:"transform"` //t:lowercase, t:uppercase, etc And []CustomRule `yaml:"and,omitempty"` Or []CustomRule `yaml:"or,omitempty"` - BodyType string `yaml:"body_type,omitempty"` + + BodyType string `yaml:"body_type,omitempty"` } func (v *CustomRule) Convert(ruleType string, appsecRuleName string) (string, []uint32, error) { diff --git a/pkg/appsec/appsec_rule/modsec_rule_test.go b/pkg/appsec/appsec_rule/modsec_rule_test.go index d919dce25f3..a11bfd214c8 100644 --- a/pkg/appsec/appsec_rule/modsec_rule_test.go +++ b/pkg/appsec/appsec_rule/modsec_rule_test.go @@ -18,6 +18,22 @@ func TestVPatchRuleString(t *testing.T) { }, expected: `SecRule ARGS_GET:foo "@rx [^a-zA-Z]" "id:2203944045,phase:2,deny,log,msg:'Base Rule',tag:'crowdsec-Base Rule',t:lowercase"`, }, + { + name: "Base Rule #2", + rule: CustomRule{ + Zones: []string{"METHOD"}, + Match: match{Type: "startsWith", Value: "toto"}, + }, + expected: `SecRule REQUEST_METHOD "@beginsWith toto" "id:2759779019,phase:2,deny,log,msg:'Base Rule #2',tag:'crowdsec-Base Rule #2'"`, + }, + { + name: "Base Negative Rule", + rule: CustomRule{ + Zones: []string{"METHOD"}, + Match: match{Type: "startsWith", Value: "toto", Not: true}, + }, + expected: `SecRule REQUEST_METHOD "!@beginsWith toto" "id:3966251995,phase:2,deny,log,msg:'Base Negative Rule',tag:'crowdsec-Base Negative Rule'"`, + }, { name: "Multiple Zones", rule: CustomRule{ diff --git a/pkg/appsec/appsec_rule/modsecurity.go b/pkg/appsec/appsec_rule/modsecurity.go index 7b98206c6c9..e9f131b5f6e 100644 --- a/pkg/appsec/appsec_rule/modsecurity.go +++ b/pkg/appsec/appsec_rule/modsecurity.go @@ -44,6 +44,7 @@ var matchMap map[string]string = map[string]string{ "lt": "@lt", "gte": "@ge", "lte": "@le", + "eq": "@eq", } var bodyTypeMatch map[string]string = map[string]string{ @@ -141,7 +142,11 @@ func (m *ModsecurityRule) buildRules(rule *CustomRule, appsecRuleName string, an if rule.Match.Type != "" { if match, ok := matchMap[rule.Match.Type]; ok { - r.WriteString(fmt.Sprintf(`"%s %s"`, match, rule.Match.Value)) + prefix := "" + if rule.Match.Not { + prefix = "!" + } + r.WriteString(fmt.Sprintf(`"%s%s %s"`, prefix, match, rule.Match.Value)) } else { return nil, fmt.Errorf("unknown match type '%s'", rule.Match.Type) } diff --git a/pkg/appsec/request.go b/pkg/appsec/request.go index f9ff4c16994..c82529acc95 100644 --- a/pkg/appsec/request.go +++ b/pkg/appsec/request.go @@ -11,6 +11,7 @@ import ( "regexp" "github.com/google/uuid" + "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" ) @@ -267,7 +268,7 @@ func (r *ReqDumpFilter) ToJSON() error { } // Generate a ParsedRequest from a http.Request. ParsedRequest can be consumed by the App security Engine -func NewParsedRequestFromRequest(r *http.Request) (ParsedRequest, error) { +func NewParsedRequestFromRequest(r *http.Request, logger *logrus.Entry) (ParsedRequest, error) { var err error contentLength := r.ContentLength if contentLength < 0 { @@ -282,26 +283,23 @@ func NewParsedRequestFromRequest(r *http.Request) (ParsedRequest, error) { } } - // the real source of the request is set in 'x-client-ip' clientIP := r.Header.Get(IPHeaderName) if clientIP == "" { return ParsedRequest{}, fmt.Errorf("missing '%s' header", IPHeaderName) } - // the real target Host of the request is set in 'x-client-host' - clientHost := r.Header.Get(HostHeaderName) - if clientHost == "" { - return ParsedRequest{}, fmt.Errorf("missing '%s' header", HostHeaderName) - } - // the real URI of the request is set in 'x-client-uri' + clientURI := r.Header.Get(URIHeaderName) if clientURI == "" { return ParsedRequest{}, fmt.Errorf("missing '%s' header", URIHeaderName) } - // the real VERB of the request is set in 'x-client-uri' clientMethod := r.Header.Get(VerbHeaderName) if clientMethod == "" { return ParsedRequest{}, fmt.Errorf("missing '%s' header", VerbHeaderName) } + clientHost := r.Header.Get(HostHeaderName) + if clientHost == "" { //this might be empty + logger.Debugf("missing '%s' header", HostHeaderName) + } // delete those headers before coraza process the request delete(r.Header, IPHeaderName)