File Coverage

blib/lib/Snort/Rule.pm
Criterion Covered Total %
statement 6 111 5.4
branch 0 56 0.0
condition 0 6 0.0
subroutine 2 16 12.5
pod 11 14 78.5
total 19 203 9.3


line stmt bran cond sub pod time code
1             package Snort::Rule;
2            
3             =head1 NAME
4            
5             Snort::Rule - Perl extension for dynamically building snort rules
6            
7             =head1 SYNOPSIS
8            
9             use Snort::Rule;
10             $rule = Snort::Rule->new(
11             -action => 'alert',
12             -proto => 'tcp',
13             -src => 'any',
14             -sport => 'any',
15             -dir => '->',
16             -dst => '192.188.1.1',
17             -dport => '44444',
18             );
19            
20             $rule->opts('msg','Test Rule"');
21             $rule->opts('threshold','type limit,track by_src,count 1,seconds 3600');
22             $rule->opts('sid','500000');
23            
24             print $rule->string()."\n";
25            
26             OR
27            
28             $rule = 'alert tcp $SMTP_SERVERS any -> $EXTERNAL_NET 25 (msg:"BLEEDING-EDGE POLICY SMTP US Top Secret PROPIN"; flow:to_server,established; content:"Subject|3A|"; pcre:"/(TOP\sSECRET|TS)//[\s\w,/-]*PROPIN[\s\w,/-]*(?=//(25)?X[1-9])/ism"; classtype:policy-violation; sid:2002448; rev:1;)';
29            
30             $rule = Snort::Rule->new(-parse => $rule);
31             print $rule->string()."\n";
32            
33             =head1 DESCRIPTION
34            
35             This is a very simple snort rule object. It was developed to allow for scripted dynamic rule creation. Ideally you could dynamically take a list of bad hosts and build an array of snort rule objects from that list. Then write that list using the string() method to a snort rules file.
36            
37             =cut
38            
39 1     1   46336 use strict;
  1         2  
  1         40  
40 1     1   5 use warnings;
  1         2  
  1         1745  
41            
42             our $VERSION = '1.07';
43            
44             # Put any options in here that require quotes around them
45             my @QUOTED_OPTIONS = ('MSG','URICONTENT','CONTENT','PCRE');
46            
47             =head1 OBJECT METHODS
48            
49             =head2 new
50            
51             Reads in the initial headers to generate a rule and constructs the snort::rule object around it.
52            
53             Accepts:
54            
55             -action => [string] ? [alert|log|pass|...] : 'alert'
56             -proto => [string] ? [ip|udp|tcp|...] : 'IP'
57             -src => [string] ? [$strIp] : 'any'
58             -sport => [int] ? [$sport] : 'any'
59             -dir => [string] ? [->|<-|<>] : '->'
60             -dst => [string] ? [$strIp] : 'any'
61             -dport => [int] ? [$dport] : 'any'
62             -opts => [hashref] ? [hashref] : '';
63            
64             -parse => $strRule # for parsing an existing rule into the object
65            
66             Returns: OBJECTREF
67            
68             =cut
69            
70             sub new {
71 0     0 1   my ($class, %parms) = @_;
72 0           my $self = {};
73 0           bless($self,$class);
74 0           $self->init(%parms);
75 0 0         $self->parseRule($parms{-parse}) if($parms{-parse});
76 0           return ($self);
77             }
78            
79             # INIT
80            
81             sub init {
82 0     0 0   my ($self,%parms) = @_;
83 0 0         $parms{-action} = $parms{-action} ? $parms{-action} : 'alert';
84 0 0         $parms{-proto} = $parms{-proto} ? $parms{-proto} : 'IP';
85 0 0         $parms{-src} = $parms{-src} ? $parms{-src} : 'any';
86 0 0         $parms{-sport} = $parms{-sport} ? $parms{-sport} : 'any';
87 0 0         $parms{-dir} = $parms{-dir} ? $parms{-dir} : '->';
88 0 0         $parms{-dst} = $parms{-dst} ? $parms{-dst} : 'any';
89 0 0         $parms{-dport} = $parms{-dport} ? $parms{-dport} : 'any';
90            
91 0 0         $parms{-opts} = '' if(!(ref($parms{-opts}) eq 'HASH'));
92            
93 0           $self->action( $parms{-action});
94 0           $self->proto( $parms{-proto});
95 0           $self->src( $parms{-src});
96 0           $self->sport( $parms{-sport});
97 0           $self->dir( $parms{-dir});
98 0           $self->dst( $parms{-dst});
99 0           $self->dport( $parms{-dport});
100 0           $self->opts( $parms{-opts});
101            
102             }
103            
104             =head2 string
105            
106             Outputs the rule in string form.
107            
108             print $sr->string()."\n";
109            
110             Prints "options only" string:
111            
112             print $sr->string(-optionsOnly => 1)."\n";
113            
114             =cut
115            
116             sub string {
117 0     0 1   my ($self,%parms) = @_;
118 0           my $rule = '';
119            
120 0 0         $rule = $self->action().' '.$self->proto().' '.$self->src().' '.$self->sport().' '.$self->dir().' '.$self->dst().' '.$self->dport().' (' unless($parms{-optionsOnly});
121 0           my @sort = sort { $a <=> $b } keys %{$self->opts()};
  0            
  0            
122 0           foreach my $key (@sort) {
123 0 0         if ($self->opts->{$key}->{opt}) {
124 0           $rule .= ' '.$self->opts->{$key}->{opt};
125 0 0 0       $rule .= ':'.$self->opts->{$key}->{val} if($self->opts->{$key}->{val} && $self->opts->{$key}->{val} ne '""');
126 0           $rule .= ';';
127             }
128             }
129 0 0         $rule .= ' )' unless($parms{-optionsOnly});
130 0 0         $rule =~ s/^ // if($parms{-optionsOnly});
131 0           return $rule;
132             }
133            
134             =head2 action
135            
136             Sets and returns the rule action [alert,log,pass,...]
137            
138             $rule->action('alert');
139            
140             =cut
141            
142             sub action {
143 0     0 1   my ($self,$v) = @_;
144 0 0         $self->{_action} = $v if(defined($v));
145 0           return $self->{_action};
146             }
147            
148             =head2 proto
149            
150             Sets and returns the protocol used in the rule [tcp,icmp,udp]
151            
152             $rule->proto('tcp');
153            
154             =cut
155            
156             sub proto {
157 0     0 1   my ($self,$v) = @_;
158 0 0         $self->{_proto} = $v if(defined($v));
159 0           return $self->{_proto};
160             }
161            
162             =head2 src
163            
164             Sets and returns the source used in the rule. Make sure you use SINGLE QUOTES for variables!!!
165            
166             $rule->src('$EXTERNAL_NET');
167            
168             =cut
169            
170             sub src {
171 0     0 1   my ($self,$v) = @_;
172 0 0         $self->{_src} = $v if(defined($v));
173 0           return $self->{_src};
174             }
175            
176             =head2 sport
177            
178             Sets and returns the source port used in the rule
179            
180             $rule->sport(80);
181            
182             =cut
183            
184             sub sport {
185 0     0 1   my ($self,$v) = @_;
186 0 0         $self->{_sport} = $v if(defined($v));
187 0           return $self->{_sport};
188             }
189            
190             =head2 dir
191            
192             Sets and returns the direction operator used in the rule, -> <- or <>
193            
194             $rule->dir('->');
195            
196             =cut
197            
198             sub dir {
199 0     0 1   my ($self,$v) = @_;
200 0 0         $self->{_dir} = $v if(defined($v));
201 0           return $self->{_dir};
202             }
203            
204             =head2 dst
205            
206             Sets and returns the destination used in the rule
207            
208             $rule->dst('$HOME_NET');
209             $rule->dst('192.168.1.1');
210            
211             =cut
212            
213             sub dst {
214 0     0 1   my ($self,$v) = @_;
215 0 0         $self->{_dst} = $v if(defined($v));
216 0           return $self->{_dst};
217             }
218            
219             =head2 dport
220            
221             Sets and returns the destination port used in the rule
222            
223             $rule->dport(6667);
224            
225             =cut
226            
227             sub dport {
228 0     0 1   my ($self,$v) = @_;
229 0 0         $self->{_dport} = $v if(defined($v));
230 0           return $self->{_dport};
231             }
232            
233             =head2 opts
234            
235             Sets an option and a value used in the rule. This currently can only be done one set at a time, and is printed in the order it was set.
236            
237             $rule->opts(option,value);
238             $rule->opts('msg','this is a test rule');
239            
240             This will return a hashref: $hashref->{$keyOrderValue}->{option} and $hashref->{$keyOrderValue}->{value}
241            
242             my $hashref = $rule->opts();
243            
244             There is a fixQuotes() function that reads through this information before setting it, just to ensure the right options are sane. It's a very very basic function, but it seems to get the job done.
245            
246             This method will also accept HASHREF's for easier use:
247            
248             $rule->opts({
249             msg => 'test1',
250             rev => '222',
251             content => 'Subject|3A|',
252             nocase => '',
253             });
254            
255             By passing an option => '', the parser will set its value to "''". When $self->string() is called, the option will be written as: option;
256             ex: nocase => '', will result in an option output of: ...., nocase; ...
257            
258             =cut
259            
260             sub opts {
261 0     0 1   my ($self,$opt,$v) = @_;
262 0 0         if (defined($opt)) {
263 0 0         if(ref($opt) eq 'HASH'){
264 0           foreach my $x (keys %$opt){
265 0           $opt->{$x} = fixQuotes($x,$opt->{$x});
266 0           my $pri = (keys %{$self->{_opts}}) + 1;
  0            
267 0           $self->{_opts}->{$pri}->{opt} = $x;
268 0           $self->{_opts}->{$pri}->{val} = $opt->{$x};
269             }
270             }
271             else {
272 0           $v = fixQuotes($opt,$v);
273 0           my $pri = (keys %{$self->{_opts}}) + 1;
  0            
274 0           $self->{_opts}->{$pri}->{opt} = $opt;
275 0           $self->{_opts}->{$pri}->{val} = $v;
276             }
277             }
278 0           return $self->{_opts};
279             }
280            
281             =head2 opt
282            
283             Gets the value of the first option with a given name.
284            
285             $rule->opt(option);
286             print $rule->opt('sid') . ': ' . $rule->opt('msg');
287            
288             =cut
289             sub opt {
290 0     0 1   my ($self,$opt) = @_;
291 0 0         if (defined($opt)) {
292 0           my @sort = sort { $a <=> $b } keys %{$self->opts()};
  0            
  0            
293 0           foreach my $key (@sort) {
294 0 0         return $self->opts->{$key}->{val} if($self->opts->{$key}->{opt} eq $opt);
295             }
296             }
297 0           return undef;
298             }
299            
300             # INTERNAL FUNCTIONS ( for now )
301            
302             sub fixQuotes {
303 0     0 0   my ($opt, $v) = @_;
304 0           foreach my $option (@QUOTED_OPTIONS) {
305 0 0 0       if (defined($v) && (uc($opt) eq $option)) {
    0          
306 0 0         if (!($v =~ /^\"\S+|\s+\"$/)) { # do we have the appropriate quotes? (anchored for pcre)
307 0           $v =~ s/^\"|\"$//g; # strip the quotes
308 0           $v = "\"$v\""; # fix em
309             }
310 0           last;
311             }
312 0           elsif(!defined($v)) { $v = "\"\""; }
313             }
314 0           return $v;
315             }
316            
317             sub parseRule {
318 0     0 0   my ($self, $rule) = @_;
319 0           my @r = split(/\(/,$rule,2);
320 0           $r[1] =~ s/\)$//;
321            
322 0           my @meta = split(/\s+/,$r[0]);
323 0           my @opts = split(/\s*;\s*/,$r[1]);
324            
325 0           $self->action( $meta[0]);
326 0           $self->proto( $meta[1]);
327 0           $self->src( $meta[2]);
328 0           $self->sport( $meta[3]);
329 0           $self->dir( $meta[4]);
330 0           $self->dst( $meta[5]);
331 0           $self->dport( $meta[6]);
332            
333 0           foreach my $x (@opts) {
334 0           my ($opt, $v) = split(/\s*:\s*/, $x, 2);
335 0           $self->opts($opt, $v);
336             }
337             }
338            
339             1;
340             __END__