Skip to content

Commit 63ad938

Browse files
authored
Issue 151: Fix handling of negated network components (#197)
* Address first part of issue #151. Updates the Nets struct to include boolean values for negation. * Remove unnecessary comments, move some comments around. * Add another test for netgated network blocks.
1 parent c9bac16 commit 63ad938

File tree

4 files changed

+90
-11
lines changed

4 files changed

+90
-11
lines changed

parser.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,14 @@ func (r *Rule) protocol(key item, l *lexer) error {
470470

471471
// network decodes an IDS rule network (networks and ports) based on its key.
472472
func (r *Rule) network(key item, l *lexer) error {
473+
// Identify if the whole network component is negated.
474+
tmp := strings.TrimPrefix(key.value, "!")
475+
negated := len(tmp) < len(key.value)
476+
473477
// This is a hack. We use a regexp to replace the outer `,` with `___`
474478
// to give us a discrete string to split on, avoiding the inner `,`.
475-
476479
// Specify TrimSuffix and TrimPrefix to ensure only one instance of `[` and `]` are trimmed.
477-
tmp := strings.TrimSuffix(strings.TrimPrefix(key.value, "["), "]")
480+
tmp = strings.TrimSuffix(strings.TrimPrefix(tmp, "["), "]")
478481
items := strings.Split(nestedNetRE.ReplaceAllString(tmp, "___${1}"), "___")
479482

480483
// Validate that no items contain spaces.
@@ -485,24 +488,28 @@ func (r *Rule) network(key item, l *lexer) error {
485488
}
486489
switch key.typ {
487490
case itemSourceAddress:
491+
r.Source.NegateNets = negated
488492
if validNetworks(items) {
489493
r.Source.Nets = append(r.Source.Nets, items...)
490494
} else {
491495
return fmt.Errorf("some or all source ips are invalid: %v", items)
492496
}
493497
case itemSourcePort:
498+
r.Source.NegatePorts = negated
494499
if portsValid(items) {
495500
r.Source.Ports = append(r.Source.Ports, items...)
496501
} else {
497502
return fmt.Errorf("some or all source ports are invalid: %v", items)
498503
}
499504
case itemDestinationAddress:
505+
r.Destination.NegateNets = negated
500506
if validNetworks(items) {
501507
r.Destination.Nets = append(r.Destination.Nets, items...)
502508
} else {
503509
return fmt.Errorf("some or all destination ips are invalid: %v", items)
504510
}
505511
case itemDestinationPort:
512+
r.Destination.NegatePorts = negated
506513
if portsValid(items) {
507514
r.Destination.Ports = append(r.Destination.Ports, items...)
508515
} else {

parser_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1979,6 +1979,56 @@ func TestParseRule(t *testing.T) {
19791979
},
19801980
},
19811981
},
1982+
{
1983+
name: "negated port list",
1984+
rule: `alert tcp any any -> any ![80,443,9000] (msg:"negated port list"; content:"123"; sid:1; rev:1;)`,
1985+
want: &Rule{
1986+
Action: "alert",
1987+
Protocol: "tcp",
1988+
Source: Network{
1989+
Nets: []string{"any"},
1990+
Ports: []string{"any"},
1991+
},
1992+
Destination: Network{
1993+
Nets: []string{"any"},
1994+
NegatePorts: true,
1995+
Ports: []string{"80,443,9000"},
1996+
},
1997+
Description: "negated port list",
1998+
Matchers: []orderedMatcher{
1999+
&Content{
2000+
Pattern: []byte("123"),
2001+
},
2002+
},
2003+
SID: 1,
2004+
Revision: 1,
2005+
},
2006+
},
2007+
{
2008+
name: "negated network list",
2009+
rule: `alert tcp ![$HOME_NET,192.168.1.1] any -> any $HTTP_PORTS (msg:"negated port list"; content:"123"; sid:1; rev:1;)`,
2010+
want: &Rule{
2011+
Action: "alert",
2012+
Protocol: "tcp",
2013+
Source: Network{
2014+
NegateNets: true,
2015+
Nets: []string{"$HOME_NET,192.168.1.1"},
2016+
Ports: []string{"any"},
2017+
},
2018+
Destination: Network{
2019+
Nets: []string{"any"},
2020+
Ports: []string{"$HTTP_PORTS"},
2021+
},
2022+
Description: "negated port list",
2023+
Matchers: []orderedMatcher{
2024+
&Content{
2025+
Pattern: []byte("123"),
2026+
},
2027+
},
2028+
SID: 1,
2029+
Revision: 1,
2030+
},
2031+
},
19822032
{
19832033
name: "raw network list",
19842034
rule: `alert tcp [174.129.0.0/16,67.202.0.0/18] any -> $HOME_NET any (msg:"raw network list"; content:"hi"; sid:12345; rev:1;)`,

rule.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,12 @@ type Metadatas []*Metadata
105105

106106
// Network describes the IP addresses and port numbers used in a rule.
107107
// TODO: Ensure all values either begin with $ (variable) or they are valid IPNet/int.
108+
// TODO: Refactor Nets and Ports into structs, each with their own bool for negation.
108109
type Network struct {
109-
Nets []string // Currently just []string because these can be variables $HOME_NET, not a valid IPNet.
110-
Ports []string // Currently just []string because these can be variables $HTTP_PORTS, not just ints.
110+
NegateNets bool // Negate the full set of Networks.
111+
Nets []string // Currently just []string because these can be variables $HOME_NET, not a valid IPNet.
112+
NegatePorts bool // Negate the full set of ports.
113+
Ports []string // Currently just []string because these can be variables $HTTP_PORTS, not just ints.
111114
}
112115

113116
// DataPos indicates the data position for content matches. These should be referenced for creation
@@ -664,9 +667,19 @@ func netString(netPart []string) string {
664667
return s.String()
665668
}
666669

667-
// String retunrs a string for a Network.
670+
// String returns a string for a Network.
668671
func (n Network) String() string {
669-
return fmt.Sprintf("%s %s", netString(n.Nets), netString(n.Ports))
672+
var s strings.Builder
673+
if n.NegateNets {
674+
s.WriteString("!")
675+
}
676+
s.WriteString(netString(n.Nets))
677+
s.WriteString(" ")
678+
if n.NegatePorts {
679+
s.WriteString("!")
680+
}
681+
s.WriteString(netString(n.Ports))
682+
return s.String()
670683
}
671684

672685
// String returns a string for a FastPattern.

rule_test.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,9 @@ func TestNetworkString(t *testing.T) {
342342
name: "complex net",
343343
input: Network{
344344
Nets: []string{"$HOME_NET", "!$FOO_NET", "192.168.0.0/16"},
345-
Ports: []string{"$HTTP_PORTS", "!53", "$BAR_NET"},
345+
Ports: []string{"$HTTP_PORTS", "!53", "$BAR_PORTS"},
346346
},
347-
want: "[$HOME_NET,!$FOO_NET,192.168.0.0/16] [$HTTP_PORTS,!53,$BAR_NET]",
347+
want: "[$HOME_NET,!$FOO_NET,192.168.0.0/16] [$HTTP_PORTS,!53,$BAR_PORTS]",
348348
},
349349
{
350350
name: "grouped ports",
@@ -357,10 +357,19 @@ func TestNetworkString(t *testing.T) {
357357
{
358358
name: "grouped networks",
359359
input: Network{
360-
Nets: []string{"192.168.0.0/16", "![192.168.86.0/24,192.168.87.0/24]"},
361-
Ports: []string{"$HTTP_PORTS", "!53", "$BAR_NET"},
360+
Nets: []string{"192.168.0.0/16", "[192.168.86.0/24,192.168.87.0/24]"},
361+
Ports: []string{"$HTTP_PORTS", "!53", "$BAR_PORTS"},
362362
},
363-
want: "[192.168.0.0/16,![192.168.86.0/24,192.168.87.0/24]] [$HTTP_PORTS,!53,$BAR_NET]",
363+
want: "[192.168.0.0/16,[192.168.86.0/24,192.168.87.0/24]] [$HTTP_PORTS,!53,$BAR_PORTS]",
364+
},
365+
{
366+
name: "negated networks",
367+
input: Network{
368+
NegateNets: true,
369+
Nets: []string{"192.168.0.0/16", "[192.168.86.0/24,192.168.87.0/24]"},
370+
Ports: []string{"$HTTP_PORTS", "!53", "$BAR_PORTS"},
371+
},
372+
want: "![192.168.0.0/16,[192.168.86.0/24,192.168.87.0/24]] [$HTTP_PORTS,!53,$BAR_PORTS]",
364373
},
365374
} {
366375
got := tt.input.String()

0 commit comments

Comments
 (0)