From bcfe96c5f0cbb2092f57b94c32d8f97feebf4c70 Mon Sep 17 00:00:00 2001 From: Bradley Kemp Date: Thu, 15 Dec 2022 14:26:45 +0000 Subject: [PATCH] Make value comparisons case insensitive (#24) --- evaluator/evaluate_test.go | 85 ++++++++++++++++++++++++++++++++++++++ evaluator/modifiers.go | 11 +++-- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/evaluator/evaluate_test.go b/evaluator/evaluate_test.go index 45293cf..be7c7f3 100644 --- a/evaluator/evaluate_test.go +++ b/evaluator/evaluate_test.go @@ -216,3 +216,88 @@ func TestRuleEvaluator_Matches_CIDR(t *testing.T) { t.Fatal("rule should have matched") } } + +func TestRuleEvaluator_MatchesCaseInsensitive(t *testing.T) { + rule := ForRule(sigma.Rule{ + Detection: sigma.Detection{ + Searches: map[string]sigma.Search{ + "foo": { + EventMatchers: []sigma.EventMatcher{ + { + { + Field: "foo-field", + Values: []string{ + "foo-value", + }, + }, + }, + }, + }, + "bar": { + EventMatchers: []sigma.EventMatcher{ + { + { + Field: "bar-field", + Values: []string{ + "bAr-VaLuE", + }, + }, + }, + }, + }, + "baz": { + EventMatchers: []sigma.EventMatcher{ + { + { + Field: "baz-field", + Values: []string{ + "baz-value", + }, + }, + }, + }, + }, + "null-field": { + EventMatchers: []sigma.EventMatcher{ + { + { + Field: "non-existent-field", + Values: []string{ + "null", + }, + }, + }, + }, + }, + }, + Conditions: []sigma.Condition{ + { + Search: sigma.And{ + sigma.SearchIdentifier{Name: "foo"}, + sigma.SearchIdentifier{Name: "bar"}, + sigma.SearchIdentifier{Name: "null-field"}, + }, + }, + { + Search: sigma.AllOfThem{}, + }, + }, + }, + }) + + result, err := rule.Matches(context.Background(), map[string]interface{}{ + "foo-field": "FoO-vAlUe", + "bar-field": "bar-value", + "baz-field": "WrOnG-vAlUe", + }) + switch { + case err != nil: + t.Fatal(err) + case !result.Match: + t.Error("rule should have matched", result.SearchResults) + case !result.SearchResults["foo"] || !result.SearchResults["bar"] || result.SearchResults["baz"]: + t.Error("expected foo and bar to be true but not baz") + case !result.ConditionResults[0] || result.ConditionResults[1]: + t.Error("expected first condition to be true and second condition to be false") + } +} diff --git a/evaluator/modifiers.go b/evaluator/modifiers.go index 47d41d2..f39d2c8 100644 --- a/evaluator/modifiers.go +++ b/evaluator/modifiers.go @@ -16,7 +16,8 @@ func baseComparator(actual interface{}, expected string) bool { // special case: "null" should match the case where a field isn't present (and so actual is nil) return true default: - return fmt.Sprintf("%v", actual) == expected + // The Sigma spec defines that by default comparisons are case-insensitive + return strings.EqualFold(fmt.Sprintf("%v", actual), expected) } } @@ -25,17 +26,19 @@ type valueModifier func(next valueComparator) valueComparator var modifiers = map[string]valueModifier{ "contains": func(_ valueComparator) valueComparator { return func(actual interface{}, expected string) bool { - return strings.Contains(fmt.Sprintf("%v", actual), expected) + // The Sigma spec defines that by default comparisons are case-insensitive + return strings.Contains(strings.ToLower(fmt.Sprintf("%v", actual)), strings.ToLower(expected)) } }, "endswith": func(_ valueComparator) valueComparator { return func(actual interface{}, expected string) bool { - return strings.HasSuffix(fmt.Sprintf("%v", actual), expected) + // The Sigma spec defines that by default comparisons are case-insensitive + return strings.HasSuffix(strings.ToLower(fmt.Sprintf("%v", actual)), strings.ToLower(expected)) } }, "startswith": func(_ valueComparator) valueComparator { return func(actual interface{}, expected string) bool { - return strings.HasPrefix(fmt.Sprintf("%v", actual), expected) + return strings.HasPrefix(strings.ToLower(fmt.Sprintf("%v", actual)), strings.ToLower(expected)) } }, "base64": func(next valueComparator) valueComparator {