From 26586bf2daf43d47843cf26a24148a05747730a9 Mon Sep 17 00:00:00 2001 From: Duane Howard Date: Wed, 5 Aug 2026 16:56:30 +0000 Subject: [PATCH 1/2] Support \| and valid escape sequences in content matching --- parser.go | 4 ++-- parser_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/parser.go b/parser.go index 78a3ac1..ca6a674 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 diff --git a/parser_test.go b/parser_test.go index 5fabdb5..95e0162 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2139,6 +2139,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 +2383,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 { From ef2c4af549beed7375041724f7bbcb4163e69abb Mon Sep 17 00:00:00 2001 From: Duane Howard Date: Wed, 5 Aug 2026 17:15:46 +0000 Subject: [PATCH 2/2] Support negated option tag values --- parser.go | 11 ++++++++++- parser_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index ca6a674..e4b39af 100644 --- a/parser.go +++ b/parser.go @@ -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 95e0162..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;)`,