From 40db7d11dd5e8b84dfcaa5b6deb6e33cfa8f35f1 Mon Sep 17 00:00:00 2001 From: sjeacopello Date: Tue, 16 Jul 2024 19:10:11 -0500 Subject: [PATCH] Made glob matching case-insensitive by default (reuse of 'lower' boolean var on creation) --- ident.go | 12 ++++++++++-- ident_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ pattern.go | 22 ++++++++++++--------- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/ident.go b/ident.go index 2cf32e7..d2ca392 100644 --- a/ident.go +++ b/ident.go @@ -273,7 +273,11 @@ func newSelectionFromMap(expr map[string]interface{}, noCollapseWS bool) (*Selec } switch pat := pattern.(type) { case string: - m, err := NewStringMatcher(mod, false, all, noCollapseWS, pat) + lower := true + if mod == TextPatternRegex { + lower = false + } + m, err := NewStringMatcher(mod, lower, all, noCollapseWS, pat) if err != nil { return nil, err } @@ -305,7 +309,11 @@ func newSelectionFromMap(expr map[string]interface{}, noCollapseWS bool) (*Selec } switch k { case reflect.String: - m, err := NewStringMatcher(mod, false, all, noCollapseWS, castIfaceToString(pat)...) + lower := true + if mod == TextPatternRegex { + lower = false + } + m, err := NewStringMatcher(mod, lower, all, noCollapseWS, castIfaceToString(pat)...) if err != nil { return nil, err } diff --git a/ident_test.go b/ident_test.go index 7cd08b6..4a45f5a 100644 --- a/ident_test.go +++ b/ident_test.go @@ -278,6 +278,52 @@ var identSelection2neg2 = ` } } ` +var identSelection4 = ` +--- +detection: + condition: selection + selection: + - winlog.event_data.ScriptBlockText|contains: + - '*wmic*shadowcopy*delete' +` + +var identSelection4pos1 = ` +{ + "event_id": 1337, + "channel": "Microsoft-Windows-PowerShell/Operational", + "task": "Execute a Remote Command", + "opcode": "On create calls", + "version": 1, + "record_id": 1559, + "winlog": { + "event_data": { + "MessageNumber": "1", + "MessageTotal": "1", + "ScriptBlockText": "someData WMic shaDOWcOpY dEleTe", + "ScriptBlockId": "ecbb39e8-1896-41be-b1db-9a33ed76314b" + } + } +} +` + +var identSelection4neg1 = ` +{ + "event_id": 1337, + "channel": "Microsoft-Windows-PowerShell/Operational", + "task": "Execute a Remote Command", + "opcode": "On create calls", + "version": 1, + "record_id": 1559, + "winlog": { + "event_data": { + "MessageNumber": "1", + "MessageTotal": "1", + "ScriptBlockText": "something normal", + "ScriptBlockId": "ecbb39e8-1896-41be-b1db-9a33ed76314b" + } + } +} +` var selectionCases = []identTestCase{ { @@ -304,6 +350,14 @@ var selectionCases = []identTestCase{ Neg: []string{identSelection2neg1, identSelection2neg2}, Example: ident2, }, + { + IdentCount: 1, + Rule: identSelection4, + IdentTypes: []identType{identSelection}, + Pos: []string{identSelection4pos1}, + Neg: []string{identSelection4neg1}, + Example: ident2, + }, } var keywordCases = []identTestCase{ diff --git a/pattern.go b/pattern.go index 8a6f757..6b4cfef 100644 --- a/pattern.go +++ b/pattern.go @@ -101,11 +101,11 @@ const ( // wildcard). // // Simga escaping rules per spec: -// * Plain backslash not followed by a wildcard can be expressed as single '\' or double backslash '\\'. For simplicity reasons the single notation is recommended. -// * A wildcard has to be escaped to handle it as a plain character: '\*' -// * The backslash before a wildcard has to be escaped to handle the value as a backslash followed by a wildcard: '\\*' -// * Three backslashes are necessary to escape both, the backslash and the wildcard and handle them as plain values: '\\\*' -// * Three or four backslashes are handled as double backslash. Four are recommended for consistency reasons: '\\\\' results in the plain value '\\' +// - Plain backslash not followed by a wildcard can be expressed as single '\' or double backslash '\\'. For simplicity reasons the single notation is recommended. +// - A wildcard has to be escaped to handle it as a plain character: '\*' +// - The backslash before a wildcard has to be escaped to handle the value as a backslash followed by a wildcard: '\\*' +// - Three backslashes are necessary to escape both, the backslash and the wildcard and handle them as plain values: '\\\*' +// - Three or four backslashes are handled as double backslash. Four are recommended for consistency reasons: '\\\\' results in the plain value '\\' func escapeSigmaForGlob(str string) string { if str == "" { // quick out if empty return "" @@ -185,6 +185,7 @@ func NewStringMatcher( matcher = append(matcher, RegexPattern{Re: re}) case TextPatternContains: // contains: puts * wildcards around the values, such that the value is matched anywhere in the field. p = handleWhitespace(p, noCollapseWS) + p = lowerCaseIfNeeded(p, lower) // In this condition, we need to ensure single backslashes, etc... are escaped correctly before throwing the globs on either side p = escapeSigmaForGlob(p) p = "*" + p + "*" @@ -192,7 +193,7 @@ func NewStringMatcher( if err != nil { return nil, err } - matcher = append(matcher, GlobPattern{Glob: &globNG, NoCollapseWS: noCollapseWS}) + matcher = append(matcher, GlobPattern{Glob: &globNG, Lowercase: lower, NoCollapseWS: noCollapseWS}) case TextPatternSuffix: p = handleWhitespace(p, noCollapseWS) matcher = append(matcher, SuffixPattern{Token: p, Lowercase: lower, NoCollapseWS: noCollapseWS}) @@ -213,6 +214,7 @@ func NewStringMatcher( // this is due, I believe, on how keywords are generally handled, where it is likely a random // string or event long message that may have additional detail/etc... p = handleWhitespace(p, noCollapseWS) + p = lowerCaseIfNeeded(p, lower) // In this condition, we need to ensure single backslashes, etc... are escaped correctly before throwing the globs on either side p = escapeSigmaForGlob(p) p = "*" + p + "*" @@ -220,16 +222,17 @@ func NewStringMatcher( if err != nil { return nil, err } - matcher = append(matcher, GlobPattern{Glob: &globNG, NoCollapseWS: noCollapseWS}) + matcher = append(matcher, GlobPattern{Glob: &globNG, Lowercase: lower, NoCollapseWS: noCollapseWS}) } else if strings.Contains(p, "*") { p = handleWhitespace(p, noCollapseWS) + p = lowerCaseIfNeeded(p, lower) // Do NOT call QuoteMeta here as we're assuming the author knows what they're doing... p = escapeSigmaForGlob(p) globNG, err := glob.Compile(p) if err != nil { return nil, err } - matcher = append(matcher, GlobPattern{Glob: &globNG, NoCollapseWS: noCollapseWS}) + matcher = append(matcher, GlobPattern{Glob: &globNG, Lowercase: lower, NoCollapseWS: noCollapseWS}) } else { p = handleWhitespace(p, noCollapseWS) matcher = append(matcher, ContentPattern{Token: p, Lowercase: lower, NoCollapseWS: noCollapseWS}) @@ -369,13 +372,14 @@ func (r RegexPattern) StringMatch(msg string) bool { // GlobPattern is similar to ContentPattern but allows for asterisk wildcards type GlobPattern struct { Glob *glob.Glob + Lowercase bool NoCollapseWS bool } // StringMatch implements StringMatcher func (g GlobPattern) StringMatch(msg string) bool { msg = handleWhitespace(msg, g.NoCollapseWS) - return (*g.Glob).Match(msg) + return (*g.Glob).Match(lowerCaseIfNeeded(msg, g.Lowercase)) } // SimplePattern is a reference type to illustrate StringMatcher