File Coverage

blib/lib/POE/Filter/Snort.pm
Criterion Covered Total %
statement 15 62 24.1
branch 0 20 0.0
condition n/a
subroutine 5 11 45.4
pod 6 6 100.0
total 26 99 26.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             POE::Filter::Snort - a POE stream filter that parses Snort logs into hashes
4              
5             =head1 SYNOPSIS
6              
7             #!/usr/bin/env perl
8              
9             use warnings; use strict;
10             use POE qw(Filter::Snort Wheel::FollowTail);
11              
12             POE::Session->create(
13             inline_states => {
14             _start => \&start_log,
15             got_rec => \&display_record,
16             },
17             );
18              
19             POE::Kernel->run();
20             exit;
21              
22             sub start_log {
23             $_[HEAP]->{watcher} = POE::Wheel::FollowTail->new(
24             Filename => "/var/log/snort/alert",
25             Filter => POE::Filter::Snort->new(),
26             InputEvent => "got_rec",
27             );
28             }
29              
30             sub display_record {
31             my $rec = $_[ARG0];
32              
33             print "Got a snort record:\n";
34             print "\tComment: $rec->{comment}\n" if exists $rec->{comment};
35             print "\tClass : $rec->{class}\n" if exists $rec->{class};
36             print "\tPrio : $rec->{priority}\n" if exists $rec->{priority};
37             if (exists $rec->{src_ip}) {
38             if (exists $rec->{src_port}) {
39             print "\tSource : $rec->{src_ip} $rec->{src_port}\n";
40             print "\tDest : $rec->{dst_ip} $rec->{dst_port}\n";
41             }
42             else {
43             print "\tSource : $rec->{src_ip}\n";
44             print "\tDest : $rec->{dst_ip}\n";
45             }
46              
47             foreach my $xref (@{$rec->{xref}}) {
48             print "\tXref : $xref\n";
49             }
50             }
51             }
52              
53             =head1 DESCRIPTION
54              
55             POE::Filter::Snort parses streams containing Snort alerts. Each alert
56             is returned as a hash containing the following fields: comment, class,
57             priority, src_ip, dst_ip, src_port, dst_port, xref, raw.
58              
59             Most fields are optional. For example, some snort alerts don't
60             contain a source and destination IP address. Those that do aren't
61             always accompanied by a source and destination port.
62              
63             The xref field refers to an array of URLs describing the alert in more
64             detail. It will always exist, but it may be empty if no URLs appear
65             in snort's configuration file.
66              
67             The raw field is an arrayref containing each original line of the
68             snort alert.
69              
70             =cut
71              
72             package POE::Filter::Snort;
73              
74 1     1   732 use warnings;
  1         1  
  1         34  
75 1     1   5 use strict;
  1         1  
  1         29  
76              
77 1     1   12 use vars qw($VERSION @ISA);
  1         2  
  1         62  
78             $VERSION = '0.031';
79             @ISA = qw(POE::Filter);
80              
81 1     1   4 use Carp qw(carp croak);
  1         2  
  1         52  
82 1     1   855 use POE::Filter::Line;
  1         2631  
  1         702  
83              
84             sub FRAMING_BUFFER () { 0 }
85             sub PARSER_STATE () { 1 }
86             sub PARSED_RECORD () { 2 }
87             sub LINE_FILTER () { 3 }
88              
89             sub STATE_OUTSIDE () { 0x02 }
90             sub STATE_INSIDE () { 0x04 }
91              
92             sub new {
93 0     0 1   my $class = shift;
94              
95             # We use a POE::Filter::Line internally.
96 0           my $line_filter = POE::Filter::Line->new();
97              
98 0           my $self = bless [
99             "", # FRAMING_BUFFER
100             STATE_OUTSIDE, # PARSER_STATE
101             { }, # PARSED_RECORD
102             $line_filter, # LINE_FILTER
103             ], $class;
104             }
105              
106             sub get_one_start {
107 0     0 1   my ($self, $stream) = @_;
108 0           $self->[LINE_FILTER]->get_one_start($stream);
109             }
110              
111             sub get_one {
112 0     0 1   my $self = shift;
113              
114 0           while (1) {
115 0           my $line = $self->[LINE_FILTER]->get_one();
116 0 0         return [ ] unless @$line;
117              
118 0           $line = $line->[0];
119              
120 0 0         if ($self->[PARSER_STATE] & STATE_OUTSIDE) {
121 0 0         next unless $line =~
122             /^\[\*\*\]\s*\[(\d+):(\d+):(\d+)\]\s*(.*?)\s*\[\*\*\]/;
123 0           $self->[PARSED_RECORD] = {
124             sid => $2,
125             rev => $3,
126             comment => $4,
127             xref => [ ],
128             raw => [ $line ],
129             };
130 0           $self->[PARSER_STATE] = STATE_INSIDE;
131 0           next;
132             }
133              
134 0           push @{ $self->[PARSED_RECORD]{raw} }, $line;
  0            
135              
136 0 0         if ($line =~ /^\s*$/) {
137 0           $self->[PARSER_STATE] = STATE_OUTSIDE;
138 0           return [ $self->[PARSED_RECORD] ];
139             }
140              
141 0 0         if ($line =~ /\[Classification:\s*(.+?)\s*\]/) {
142 0           $self->[PARSED_RECORD]{class} = $1;
143             }
144              
145 0 0         if ($line =~ /\[Priority:\s*(.+?)\s*\]/) {
146 0           $self->[PARSED_RECORD]{priority} = $1;
147             }
148              
149 0 0         if (
    0          
150             $line =~ m{
151             ^\d+\/\d+-\d+:\d+:\d+\.\d+\s*
152             (\d+\.\d+\.\d+\.\d+):(\d+) # src ipv4 : port
153             \s*->\s*
154             (\d+\.\d+\.\d+\.\d+):(\d+) # dst ipv4 : port
155             }x
156             ) {
157 0           $self->[PARSED_RECORD]{src_ip} = $1;
158 0           $self->[PARSED_RECORD]{src_port} = $2;
159 0           $self->[PARSED_RECORD]{dst_ip} = $3;
160 0           $self->[PARSED_RECORD]{dst_port} = $4;
161             }
162             elsif (
163             $line =~ m{
164             ^\d+\/\d+-\d+:\d+:\d+\.\d+\s*
165             (\d+\.\d+\.\d+\.\d+) # src ipv4
166             \s*->\s*
167             (\d+\.\d+\.\d+\.\d+) # dst ipv4
168             }x
169             ) {
170 0           $self->[PARSED_RECORD]{src_ip} = $1;
171 0           $self->[PARSED_RECORD]{dst_ip} = $2;
172             }
173              
174 0           while ($line =~ /\[Xref\s*=>\s*(.*?)\s*\]/g) {
175 0           push @{$self->[PARSED_RECORD]{xref}}, $1;
  0            
176             }
177              
178 0 0         die $line if $line =~ /<-/;
179             }
180             }
181              
182             sub put {
183 0     0 1   my $self = shift;
184 0           croak ref($self) . " doesn't implement put()";
185             }
186              
187             sub get_pending {
188 0     0 1   my $self = shift;
189 0           return $self->[LINE_FILTER]->get_pending();
190             }
191              
192             sub get {
193 0     0 1   my ($self, $stream) = @_;
194 0           my @return;
195              
196 0           $self->get_one_start($stream);
197 0           while (1) {
198 0           my $next = $self->get_one();
199 0 0         last unless @$next;
200 0           push @return, @$next;
201             }
202              
203 0           return \@return;
204             }
205              
206             1;
207              
208             =head1 SEE ALSO
209              
210             Snort - "the de facto standard for intrusion detection/prevention"
211             http://www.snort.org/
212              
213             L
214              
215             =head1 BUG TRACKER
216              
217             https://rt.cpan.org/Dist/Display.html?Status=Active&Queue=POE-Filter-Snort
218              
219             =head1 REPOSITORY
220              
221             http://github.com/rcaputo/poe-filter-snort
222             http://gitorious.org/poe-filter-snort
223              
224             =head1 OTHER RESOURCES
225              
226             http://search.cpan.org/dist/POE-Filter-Snort/
227              
228             =head1 COPYRIGHT
229              
230             Copyright 2005-2010, Rocco Caputo. All rights are reserved.
231              
232             POE::Filter::Snort is free software; you may use, redistribute, and/or
233             modify it under the same terms as Perl itself.
234              
235             =cut