diff --git a/parser.go b/parser.go index 78a3ac1..e4b39af 100644 --- a/parser.go +++ b/parser.go @@ -36,7 +36,7 @@ var hexRE = regexp.MustCompile(`(?i)(\|(?:\s*[a-f0-9]{2}\s*)+\|)`) var escapeRE = regexp.MustCompile(`([()+.'\\])`) // escapeContent matches escaped special characters. -var escapeContent = regexp.MustCompile(`\\([\\;":])`) +var escapeContent = regexp.MustCompile(`\\([\\;":|\[\]+ .])`) // metaSplitRE matches string in metadata. var metaSplitRE = regexp.MustCompile(`,\s*`) @@ -386,7 +386,7 @@ func containsUnescaped(s string) bool { for _, b := range s { if esc { switch b { - case '\\', ';', '"', ':': + case '\\', ';', '"', ':', '|', '[', ']', '+', '.', ' ': esc = false default: return true @@ -630,13 +630,22 @@ func (r *Rule) option(key item, l *lexer) error { "dce_iface", "dce_opnum", "dce_stub_data", "asn1"}): nextItem := l.nextItem() + negate := false + if nextItem.typ == itemNot { + negate = true + nextItem = l.nextItem() + } if nextItem.typ != itemOptionValue { return fmt.Errorf("no valid value for %s tag", key.value) } if r.Tags == nil { r.Tags = make(map[string]string) } - r.Tags[key.value] = nextItem.value + if negate { + r.Tags[key.value] = "!" + nextItem.value + } else { + r.Tags[key.value] = nextItem.value + } case inSlice(key.value, []string{"sameip", "tls.store", "ftpbounce"}): r.Statements = append(r.Statements, key.value) case inSlice(key.value, tlsTags): diff --git a/parser_test.go b/parser_test.go index 5fabdb5..0eae640 100644 --- a/parser_test.go +++ b/parser_test.go @@ -604,6 +604,31 @@ func TestParseRule(t *testing.T) { }, }, }, + { + name: "negated tags", + rule: `alert tcp any any -> any any (msg:"test"; fragbits:!M; flags:!F; tos:!0; ipopts:!rr; sid:1; rev:1;)`, + want: &Rule{ + Action: "alert", + Protocol: "tcp", + Source: Network{ + Nets: []string{"any"}, + Ports: []string{"any"}, + }, + Destination: Network{ + Nets: []string{"any"}, + Ports: []string{"any"}, + }, + SID: 1, + Revision: 1, + Description: "test", + Tags: map[string]string{ + "fragbits": "!M", + "flags": "!F", + "tos": "!0", + "ipopts": "!rr", + }, + }, + }, { name: "commented rule content", rule: `#alert udp $HOME_NET any -> $EXTERNAL_NET any (sid:1337; msg:"foo"; content:"AA"; rev:2;)`, @@ -2139,6 +2164,25 @@ func TestParseRule(t *testing.T) { }, }, }, + { + name: "content match with escaped pipe and characters", + rule: `alert tcp any any -> any any (msg:"test"; content:"\|test\| \[abc\] \+ \. \ "; sid:1; rev:1;)`, + want: &Rule{ + Action: "alert", + Protocol: "tcp", + Source: Network{Nets: []string{"any"}, Ports: []string{"any"}}, + Destination: Network{Nets: []string{"any"}, Ports: []string{"any"}}, + SID: 1, + Revision: 1, + Description: "test", + Matchers: []orderedMatcher{ + &Content{ + DataPosition: pktData, + Pattern: []byte("|test| [abc] + . "), + }, + }, + }, + }, // Errors { name: "invalid action", @@ -2364,6 +2408,21 @@ func TestContainsUnescaped(t *testing.T) { input: `\\\\\\\\\;`, want: false, }, + { + name: "escaped pipe", + input: `\|`, + want: false, + }, + { + name: "escaped bracket", + input: `\[`, + want: false, + }, + { + name: "escaped plus dot space", + input: `\+\.\ `, + want: false, + }, } { got := containsUnescaped(tt.input) if got != tt.want {