]>
git.ipfire.org Git - people/stevee/guardian.git/blob - modules/Parser.pm
1 package Guardian
::Parser
;
5 use Exporter
qw(import);
7 our @EXPORT_OK = qw(IsSupportedParser Parser);
9 # This hash contains all supported parsers and which function
10 # has to be called to parse messages in the right way.
11 my %logfile_parsers = (
12 "httpd" => \
&message_parser_httpd
,
13 "owncloud" => \
&message_parser_owncloud
,
14 "snort" => \
&message_parser_snort
,
15 "ssh" => \
&message_parser_ssh
,
19 ## The "Init" (Parser) function.
21 ## This function is responsible to initialize the Parser as a class based object.
22 ## It has to be called once before any parsing of messages can be done.
25 my ( $class, %args ) = @_;
28 # Use bless to make "$self" to an object of class "$class".
31 # Return the class object.
36 ## The "Update" Parser settings function.
38 ## This object based function is called to update various class settings.
43 # Dereference the given hash-ref and store
44 # the values into a new temporary hash.
45 my %settings = %{ $_[0] };
47 # Update snort priority level settings or disable it.
48 if ((defined($self->{SnortPriorityLevel
})) && (exists($settings{SnortPriorityLevel
}))) {
50 $self->{SnortPriorityLevel
} = $settings{SnortPriorityLevel
};
53 delete $self->{SnortPriorityLevel
};
56 # Return modified class object.
61 ## The main parsing function.
63 ## It is used to determine which sub-parser has to be used to
64 ## parse the given message in the right way and to return if
65 ## any action should be performed.
69 my ($parser, @message) = @_;
71 # If no responsible message parser could be found, just return nothing.
72 unless (exists($logfile_parsers{$parser})) {
76 # Call responsible message parser.
77 my @actions = $logfile_parsers{$parser}->($self, @message);
79 # In case an action has been returned, return it too.
81 # Return which actions should be performed.
85 # Return undef, if no actions are required.
90 ## IsSupportedParser function.
92 ## This very tiny function checks if a given parser name is available and
93 ## therefore a supported parser.
95 ## To perform these check, the function is going to lookup if a key in the
96 ## hash of supported parsers is available
98 sub IsSupportedParser
($) {
101 # Check if a key for the given parser exists in the hash of logfile_parsers.
102 if(exists($logfile_parsers{$parser})) {
103 # Found a valid parser, so return nothing.
107 # Return "False" if we got here, and therefore no parser
113 ## The Snort message parser.
115 ## This subfunction is responsible for parsing sort alerts and determine if
116 ## an action should be performed.
118 ## XXX Currently the parser only supports IPv4. Add support for IPv6 at a
121 sub message_parser_snort
(@
) {
126 # Temporary array to store single alerts.
129 # The name of the parser module.
132 # Default returned message in case no one could be grabbed
133 # from the snort alert.
134 my $message = "An active snort rule has matched and gained an alert.";
136 # Snort uses a log buffer and a result of this, when detecting multiple
137 # events at once, multiple alerts will be written at one time to the alert
138 # file. They have to be seperated from each, to be able to parse them
140 foreach my $line (@message) {
141 # Remove any newlines.
144 # A single alert contains multiple lines, push all of them
148 # Each alert ends with an empty line, if one is found,
149 # all lines of the current processed alert have been found
150 # and pushed to the temporary array.
151 if($line =~ /^\s*$/) {
152 # Variable to store the grabbed IP-address.
156 # Loop through all lines of the current alert.
157 foreach my $line (@alert) {
158 # Determine if the alert has been classified.
159 if ($line =~ /.*\[Classification: .*\] \[Priority: (\d+)\].*/) {
162 # Set classification to true.
163 $classification = "1";
165 # Obtain configured priority level.
166 my $priority_level = $self->{SnortPriorityLevel
};
168 # Skip alerts if the priority is to low.
169 if ($priority < $priority_level) {
174 # Search for a line like xxx.xxx.xxx.xxx -> xxx.xxx.xxx.xxx
175 if ($line =~ /(\d+\.\d+\.\d+\.\d+)+ -\> (\d+\.\d+\.\d+\.\d+)+/) {
176 # Store the grabbed IP-address.
180 # Search for a line like xxx.xxx.xxx.xxx:xxx -> xxx.xxx.xxx.xxx:xxx
181 elsif ($line =~ /(\d+\.\d+\.\d+\.\d+):\d+ -\> (\d+\.\d+\.\d+\.\d+):\d+/) {
182 # Store the obtained IP-address.
186 # Grab the reason from a msg field of the alert.
187 if ($line =~ /.*msg:\"(.*)\".*/) {
188 # Store the extracted message.
192 # If the reason could not be determined, try to obtain it from the headline of the alert.
193 elsif ($line =~ /.*\] (.*) \[\*\*\]/) {
194 # Store the extracted message.
199 # Check if at least the IP-address information has been extracted.
200 if ((defined ($classification)) && (defined ($address))) {
201 # Add the extracted values and event message for the computed
202 # event to the actions array.
203 push(@actions, "count $address $name $message");
206 # The alert has been processed, clear the temporary array for storing
212 # If any actions are required, return the array.
217 # If we got here, the alert could not be parsed correctly, or did not match any filter.
218 # Therefore it can be skipped - return nothing.
223 ## The SSH message parser.
225 ## This subfunction is used for parsing and detecting different attacks
226 ## against the SSH service.
228 sub message_parser_ssh
(@
) {
233 # The name of the parser module.
236 # Variable to store the grabbed IP-address.
239 # Variable to store the parsed event.
242 # Loop through all lines, in case multiple one have
244 foreach my $line (@message) {
245 # Check for failed password attempts.
246 if ($line =~/.*sshd.*Failed password for (.*) from (.*) port.*/) {
247 # Store the grabbed IP-address.
251 $message = "Possible SSH-Bruteforce Attack for user: $1.";
254 # This should catch Bruteforce Attacks with enabled preauth
255 elsif ($line =~ /.*sshd.*Received disconnect from (.*):.*\[preauth\]/) {
256 # Store obtained IP-address.
260 $message = "Possible SSH-Bruteforce Attack - failed preauth.";
263 # Check if at least the IP-address information has been extracted.
264 if (defined ($address)) {
265 # Add the extracted values and event message for the computed
266 # event to the actions array.
267 push(@actions, "count $address $name $message");
271 # If any actions are required, return the array.
276 # If we got here, the provided message is not affected by any filter and
277 # therefore can be skipped. Return nothing (False) in this case.
282 ## The HTTPD message parser.
284 ## This subfunction is used for parsing and detecting different attacks
285 ## against a running HTTPD service.
287 sub message_parser_httpd
(@
) {
292 # The name of the parser module.
295 # Variable to store the grabbed IP-address.
298 # Variable to store the parsed event.
301 # Loop through all lines, in case multiple one have
303 foreach my $line (@message) {
304 # This will catch brute-force attacks against htaccess logins (username).
305 if ($line =~ /.*\[client (.*)\] .* user(.*) not found:.*/) {
306 # Store the grabbed IP-address.
310 $message = "Possible WUI brute-force attack, wrong user: $2.";
313 # Detect htaccess password brute-forcing against a username.
314 elsif ($line =~ /.*\[client (.*)\] .* user(.*): authentication failure for.*/) {
315 # Store the extracted IP-address.
319 $message = "Possible WUI brute-force attack, wrong password for user: $2.";
322 # Check if at least the IP-address information has been extracted.
323 if (defined ($address)) {
324 # Check if the address also contains a port value.
325 if ($address =~ m/:/) {
326 my ($add_address, $port) = split(/:/, $address);
328 # Only process the address.
329 $address = $add_address;
332 # Add the extracted values and event message to the actions array.
333 push(@actions, "count $address $name $message");
337 # If any actions are required, return the array.
342 # If we got here, the provided message is not affected by any filter and
343 # therefore can be skipped. Return nothing (False) in this case.
348 ## The Owncloud message parser.
350 ## This subfunction is used for parsing and detecting brute-force login
351 ## attempts against a local running owncloud instance.
353 sub message_parser_owncloud
(@
) {
357 # The name of the parser module.
358 my $name = "Owncloud";
360 # Variable to store the grabbed IP-address.
363 # Variable to store the parsed event.
366 # Loop through all lines, in case multiple one have
368 foreach my $line (@message) {
369 # This will catch brute-force attacks against the login (username).
370 if ($line =~/.*\"Login failed: \'(.*)\' \(Remote IP: \'(.*)\'\,.*/) {
371 # Store the grabbed user name.
374 # Store the grabbed IP-address.
378 $message = "Possible brute-force attack, wrong password for user: $user.";
381 # Check if at least the IP-address information has been extracted.
382 if (defined ($address)) {
383 # Add the extracted values and event message to the actions array.
384 push(@actions, "count $address $name $message");
388 # If any actions are required, return the array.
393 # If we got here, the provided message is not affected by any filter and
394 # therefore can be skipped. Return nothing (False) in this case.