Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made glob matching case-insensitive by default (reuse of 'lower' boolean var on creation) #37

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions ident.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
54 changes: 54 additions & 0 deletions ident_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
{
Expand All @@ -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{
Expand Down
22 changes: 13 additions & 9 deletions pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down Expand Up @@ -185,14 +185,15 @@ 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 + "*"
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})
case TextPatternSuffix:
p = handleWhitespace(p, noCollapseWS)
matcher = append(matcher, SuffixPattern{Token: p, Lowercase: lower, NoCollapseWS: noCollapseWS})
Expand All @@ -213,23 +214,25 @@ 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 + "*"
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 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})
Expand Down Expand Up @@ -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
Expand Down
Loading