Sebastien Blot 1 год назад
Родитель
Сommit
b0e7da06b9

+ 8 - 1
pkg/waf/request.go

@@ -64,6 +64,7 @@ type ParsedRequest struct {
 	Host             string
 	ClientIP         string
 	URI              string
+	Args             url.Values
 	ClientHost       string
 	Headers          http.Header
 	URL              *url.URL
@@ -117,18 +118,24 @@ func NewParsedRequestFromRequest(r *http.Request) (ParsedRequest, error) {
 	delete(r.Header, URIHeaderName)
 	delete(r.Header, VerbHeaderName)
 
+	parsedURL, err := url.Parse(clientURI)
+	if err != nil {
+		return ParsedRequest{}, fmt.Errorf("unable to parse url '%s': %s", clientURI, err)
+	}
+
 	return ParsedRequest{
 		RemoteAddr:       r.RemoteAddr,
 		UUID:             uuid.New().String(),
 		ClientHost:       clientHost,
 		ClientIP:         clientIP,
-		URI:              clientURI,
+		URI:              parsedURL.Path,
 		Method:           clientMethod,
 		Host:             r.Host,
 		Headers:          r.Header,
 		URL:              r.URL,
 		Proto:            r.Proto,
 		Body:             body,
+		Args:             parsedURL.Query(), //TODO: Check if there's not potential bypass as it excludes malformed args
 		TransferEncoding: r.TransferEncoding,
 		ResponseChannel:  make(chan WaapTempResponse),
 	}, nil

+ 51 - 1
pkg/waf/waap_rule/modsec_rule_test.go

@@ -50,11 +50,61 @@ func TestVPatchRuleString(t *testing.T) {
 			expected: `SecRule ARGS_GET:foo "@rx [^a-zA-Z]" "id:2323451654,phase:2,deny,log,msg:'Basic AND_and_0',t:lowercase,chain"
 SecRule ARGS_GET:bar "@rx [^a-zA-Z]" "id:2075918819,phase:2,deny,log,msg:'Basic AND_and_1',t:lowercase"`,
 		},
+		{
+			name: "Basic OR",
+			rule: CustomRule{
+				Or: []CustomRule{
+					{
+						Zones:     []string{"ARGS"},
+						Variables: []string{"foo"},
+						Match:     match{Type: "regex", Value: "[^a-zA-Z]"},
+						Transform: []string{"lowercase"},
+					},
+					{
+						Zones:     []string{"ARGS"},
+						Variables: []string{"bar"},
+						Match:     match{Type: "regex", Value: "[^a-zA-Z]"},
+						Transform: []string{"lowercase"},
+					},
+				},
+			},
+			expected: `SecRule ARGS_GET:foo "@rx [^a-zA-Z]" "id:2720972114,phase:2,deny,log,msg:'Basic OR_or_0',t:lowercase,skip:1"
+SecRule ARGS_GET:bar "@rx [^a-zA-Z]" "id:2638639999,phase:2,deny,log,msg:'Basic OR_or_1',t:lowercase"`,
+		},
+		{
+			name: "OR AND mix",
+			rule: CustomRule{
+				And: []CustomRule{
+					{
+						Zones:     []string{"ARGS"},
+						Variables: []string{"foo"},
+						Match:     match{Type: "regex", Value: "[^a-zA-Z]"},
+						Transform: []string{"lowercase"},
+						Or: []CustomRule{
+							{
+								Zones:     []string{"ARGS"},
+								Variables: []string{"foo"},
+								Match:     match{Type: "regex", Value: "[^a-zA-Z]"},
+								Transform: []string{"lowercase"},
+							},
+							{
+								Zones:     []string{"ARGS"},
+								Variables: []string{"bar"},
+								Match:     match{Type: "regex", Value: "[^a-zA-Z]"},
+								Transform: []string{"lowercase"},
+							},
+						},
+					},
+				},
+			},
+			expected: `SecRule ARGS_GET:foo "@rx [^a-zA-Z]" "id:2720972114,phase:2,deny,log,msg:'Basic OR_or_0',t:lowercase,skip:1"
+SecRule ARGS_GET:bar "@rx [^a-zA-Z]" "id:2638639999,phase:2,deny,log,msg:'Basic OR_or_1',t:lowercase"`,
+		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			actual, err := tt.rule.Convert(ModsecurityRuleType, tt.name)
+			actual, _, err := tt.rule.Convert(ModsecurityRuleType, tt.name)
 
 			if err != nil {
 				t.Errorf("Error converting rule: %s", err)

+ 21 - 10
pkg/waf/waap_rule/modsecurity.go

@@ -7,6 +7,7 @@ import (
 )
 
 type ModsecurityRule struct {
+	id uint32
 }
 
 var zonesMap map[string]string = map[string]string{
@@ -42,18 +43,19 @@ var matchMap map[string]string = map[string]string{
 	"le":              "@le",
 }
 
-func (m ModsecurityRule) Build(rule *CustomRule, waapRuleName string) (string, error) {
+func (m *ModsecurityRule) Build(rule *CustomRule, waapRuleName string) (string, uint32, error) {
 
-	rules, err := m.buildRules(rule, waapRuleName, false)
+	rules, err := m.buildRules(rule, waapRuleName, false, 0)
 
 	if err != nil {
-		return "", err
+		return "", 0, err
 	}
 
-	return strings.Join(rules, "\n"), nil
+	//We return the id of the first generated rule, as it's the interesting one in case of chain or skip
+	return strings.Join(rules, "\n"), m.id, nil
 }
 
-func (m ModsecurityRule) generateRuleID(rule *CustomRule, waapRuleName string) uint32 {
+func (m *ModsecurityRule) generateRuleID(rule *CustomRule, waapRuleName string) uint32 {
 	h := fnv.New32a()
 	h.Write([]byte(waapRuleName))
 	h.Write([]byte(rule.Match.Type))
@@ -64,17 +66,21 @@ func (m ModsecurityRule) generateRuleID(rule *CustomRule, waapRuleName string) u
 	for _, transform := range rule.Transform {
 		h.Write([]byte(transform))
 	}
-	return h.Sum32()
+	id := h.Sum32()
+	if m.id == 0 {
+		m.id = id
+	}
+	return id
 }
 
-func (m ModsecurityRule) buildRules(rule *CustomRule, waapRuleName string, and bool) ([]string, error) {
+func (m *ModsecurityRule) buildRules(rule *CustomRule, waapRuleName string, and bool, toSkip int) ([]string, error) {
 	ret := make([]string, 0)
 
 	if rule.And != nil {
 		for c, andRule := range rule.And {
 			subName := fmt.Sprintf("%s_and_%d", waapRuleName, c)
-			lastRule := c == len(rule.And)-1
-			rules, err := m.buildRules(&andRule, subName, !lastRule)
+			lastRule := c == len(rule.And)-1 // || len(rule.Or) == 0
+			rules, err := m.buildRules(&andRule, subName, !lastRule, 0)
 			if err != nil {
 				return nil, err
 			}
@@ -85,7 +91,8 @@ func (m ModsecurityRule) buildRules(rule *CustomRule, waapRuleName string, and b
 	if rule.Or != nil {
 		for c, orRule := range rule.Or {
 			subName := fmt.Sprintf("%s_or_%d", waapRuleName, c)
-			rules, err := m.buildRules(&orRule, subName, false)
+			skip := len(rule.Or) - c - 1
+			rules, err := m.buildRules(&orRule, subName, false, skip)
 			if err != nil {
 				return nil, err
 			}
@@ -145,6 +152,10 @@ func (m ModsecurityRule) buildRules(rule *CustomRule, waapRuleName string, and b
 		r.WriteString(",chain")
 	}
 
+	if toSkip > 0 {
+		r.WriteString(fmt.Sprintf(",skip:%d", toSkip))
+	}
+
 	r.WriteByte('"')
 
 	ret = append(ret, r.String())

+ 7 - 6
pkg/waf/waap_rule/waap_rule.go

@@ -42,24 +42,25 @@ type CustomRule struct {
 	Or        []CustomRule `yaml:"or,omitempty"`
 }
 
-func (v *CustomRule) Convert(ruleType string, waapRuleName string) (string, error) {
+func (v *CustomRule) Convert(ruleType string, waapRuleName string) (string, uint32, error) {
 
 	if v.Zones == nil && v.And == nil && v.Or == nil {
-		return "", fmt.Errorf("no zones defined")
+		return "", 0, fmt.Errorf("no zones defined")
 	}
 
 	if v.Match.Type == "" && v.And == nil && v.Or == nil {
-		return "", fmt.Errorf("no match type defined")
+		return "", 0, fmt.Errorf("no match type defined")
 	}
 
 	if v.Match.Value == "" && v.And == nil && v.Or == nil {
-		return "", fmt.Errorf("no match value defined")
+		return "", 0, fmt.Errorf("no match value defined")
 	}
 
 	switch ruleType {
 	case ModsecurityRuleType:
-		return ModsecurityRule{}.Build(v, waapRuleName)
+		r := ModsecurityRule{}
+		return r.Build(v, waapRuleName)
 	default:
-		return "", fmt.Errorf("unknown rule format '%s'", ruleType)
+		return "", 0, fmt.Errorf("unknown rule format '%s'", ruleType)
 	}
 }