]> git.ipfire.org Git - people/stevee/guardian.git/commitdiff
Merge branch 'parser-snort'
authorStefan Schantl <stefan.schantl@ipfire.org>
Tue, 14 Jun 2016 13:28:14 +0000 (15:28 +0200)
committerStefan Schantl <stefan.schantl@ipfire.org>
Tue, 14 Jun 2016 13:28:14 +0000 (15:28 +0200)
guardian
modules/Config.pm
modules/Events.pm
modules/Logger.pm
modules/Parser.pm
modules/Socket.pm

index 691ddc66e1f0013add0fd5245f7864d70931d049..d01612525dfe2ec2332b3aefc6afd3b74dd8f111 100644 (file)
--- a/guardian
+++ b/guardian
@@ -2,7 +2,7 @@
 ###############################################################################
 #                                                                             #
 # IPFire.org - A linux based firewall                                         #
-# Copyright (C) 2015  IPFire Development Team                                 #
+# Copyright (C) 2015-2016  IPFire Development Team                            #
 #                                                                             #
 # This program is free software: you can redistribute it and/or modify        #
 # it under the terms of the GNU General Public License as published by        #
@@ -247,23 +247,24 @@ sub Worker ($) {
                        close(FILE);
 
                        # Send filename and message to the parser,
-                       # which will return if an action has to be performed.
-                       my @action = &Guardian::Parser::Parser("$parser", @message);
+                       # which will return if any actions have to be performed.
+                       my @actions = &Guardian::Parser::Parser("$parser", @message);
 
                        # Send the action to the main process and put it into
                        # the queue.
-                       if (@action) {
+                       if (@actions) {
                                # Lock the queue.
                                lock($queue);
 
-                               # Put the required action into the queue.
-                               $queue->enqueue(@action);
-                       }
-                       # If no action is returned by the Parser, the message
-                       # could not be parser corretly.
-                       else {
-                               # Log failed parse attempt.
-                               $logger->Log("err", "Error parsing event: \[$parser - @message\]");
+                               # Loop through the actions array, and perform
+                               # every single action.
+                               foreach my $action (@actions) {
+                                       # Prevent from enqueuing empty actions.
+                                       if (defined($action)) {
+                                               # Put the required action into the queue.
+                                               $queue->enqueue($action);
+                                       }
+                               }
                        }
                } else {
                        # Sleep for 10ms until the next round of the loop will start.
@@ -285,7 +286,7 @@ sub Worker ($) {
 #
 sub Socket () {
        # Create the Server socket by calling the responsible function.
-       my $server = &Guardian::Socket::Server();
+       my $server = &Guardian::Socket::Server($mainsettings{SocketOwner});
 
        # Log successfull creation of socket.
        $logger->Log("debug", "Listening to Socket...");
@@ -332,6 +333,7 @@ sub SignalHandler {
        $SIG{TERM} = \&Shutdown;
        $SIG{QUIT} = \&Shutdown;
        $SIG{HUP} = \&Reload;
+       $SIG{USR1} = \&ReloadIgnoreList;
 }
 
 #
@@ -394,7 +396,7 @@ sub Reload () {
        $mainsettings{Logger} = $logger;
 
        # Update ignore list.
-       &Guardian::Events::GenerateIgnoreList($mainsettings{IgnoreFile});
+       &ReloadIgnoreList();
 
        # Re-generate hash of monitored files.
        %monitored_files = &Guardian::Base::GenerateMonitoredFiles(\%mainsettings, \%monitored_files);
@@ -403,6 +405,25 @@ sub Reload () {
        &StartWorkers();
 }
 
+#
+## ReloadIgnoreList function.
+#
+## This function will be called if the signal handler recieves a "SIGUSR1" signal,
+## or the reload-ignore-list command will be sent via the socket connection. It just
+## performs a simple check if an ignore file has been configured and calls the responsible
+## function on the events module.
+#
+sub ReloadIgnoreList () {
+       # Update ignore list, if an ignorefile has been specified.
+       if (exists($mainsettings{IgnoreFile})) {
+               # Log reload of the ignore list.
+               $logger->Log("info", "Reloading ignore list...");
+
+               # Call responsible function from the events module.
+               &Guardian::Events::GenerateIgnoreList($mainsettings{IgnoreFile});
+       }
+}
+
 #
 ## Shutdown function.
 #
index 8da587cde15eb3357add35718f18d1b5ccdce708..75f1ab02496be95b735bd46b8f0d2b695fccd66c 100644 (file)
@@ -162,6 +162,17 @@ sub CheckConfig (\%) {
                return "Invalid LogLevel: $config{LogLevel}";
        }
 
+       # Check if an optional configured SocketOwner is valid.
+       if (exists($config{SocketOwner})) {
+               my ($user, $group) = split(/:/, $config{SocketOwner});
+
+               # Get the ID for the given user name.
+               my $uid = getpwnam($user) or return "The user $user does not exist.";
+
+               # Get the ID for given group name.
+               my $gid = getgrnam($group) or return "The group $group does not exist.";
+       } 
+
        # The config looks good, so return nothing (no error message).
        return undef
 }
index 025fe4f243fa4e685d3dabb6cf83a97ccd22feb3..376438fb25681119ac6df1537e046d8994f01a1d 100644 (file)
@@ -13,6 +13,7 @@ my %commands = (
        'unblock' => \&CallUnblock,
        'flush' => \&CallFlush,
        'reload' => \&main::Reload,
+       'reload-ignore-list' => \&main::ReloadIgnoreList,
 );
 
 # Hash to store addresses and their current count.
@@ -303,6 +304,11 @@ sub CallUnblock ($) {
 #
 sub GenerateIgnoreList($) {
        my $file = shift;
+       my @include_files;
+
+       # Reset current ignore hash and add
+       # localhost related IP addresses.
+       %ignorehash = &_whitelist_localhost();
 
        # Check if the given IgnoreFile could be opened.
        unless(-e $file) {
@@ -310,11 +316,7 @@ sub GenerateIgnoreList($) {
                return;
        }
 
-       # Reset current ignore hash and add
-       # localhost related IP addresses.
-       %ignorehash = &_whitelist_localhost();
-
-       # Open the given IgnoreFile. 
+       # Open the given IgnoreFile.
        open (IGNORE, $file);
 
        # Read-in the file line by line.
@@ -328,29 +330,89 @@ sub GenerateIgnoreList($) {
                # Remove any newlines.
                chomp;
 
-               # Check if the line contains a valid single address or network and
-               # convert it into binary format. Store the result/start and
-               # end values in a temporary array.
-               my @values = &Guardian::Base::IPOrNet2Int($_);
+               # Check for an include instruction.
+               if ($_ =~ /^Include_File = (.*)/) {
+                       my $include_file = $1;
 
-               # If the function returned any values, the line contained a valid
-               # single address or network which successfully has been converted into
-               # binary format.
-               if (@values) {
-                       # Assign the array as value to the ignorehash.
-                       $ignorehash{$_} = [@values];
-               } else {
-                       # Log invalid entry.
-                       $logger->Log("err", "IgnoreFile contains an invalid address/network: $_");
+                       # Check if the parsed include file exists and is read-able.
+                       if (-e $include_file) {
+                               # Add file to the array of files wich will be included.
+                               push(@include_files, $include_file);
 
-                       # Skip line.
-                       next;
+                               # Write out log message.
+                               $logger->Log("debug", "Addresses from $include_file will be included...");
+                       } else {
+                               # Log missing file.
+                               $logger->Log("err", "$include_file will not be included. File does not exist!");
+                       }
+               } else {
+                       # Check if the line contains a valid single address or network and
+                       # convert it into binary format. Store the result/start and
+                       # end values in a temporary array.
+                       my @values = &Guardian::Base::IPOrNet2Int($_);
+
+                       # If the function returned any values, the line contained a valid
+                       # single address or network which successfully has been converted into
+                       # binary format.
+                       if (@values) {
+                               # Assign the array as value to the ignorehash.
+                               $ignorehash{$_} = [@values];
+                       } else {
+                               # Log invalid entry.
+                               $logger->Log("err", "IgnoreFile contains an invalid address/network: $_");
+
+                               # Skip line.
+                               next;
+                       }
                }
        }
 
        # Close filehandle for the IgnoreFile.
        close (IGNORE);
 
+       # Check if any files should be included.
+       if (@include_files) {
+               # Loop through the array of files which should be included.
+               foreach my $file (@include_files) {
+                       # Open the file.
+                       open(INCLUDE, $file);
+
+                       # Read-in file line by line.
+                       while(<INCLUDE>) {
+                               # Skip any comments.
+                               next if (/\#/);
+
+                               # Skip any blank lines.
+                               next if (/^\s*$/);
+
+                               # Chomp any newlines.
+                               chomp;
+
+                               # Check if the line contains a valid single address or network and
+                               # convert it into binary format. Store the result/start and
+                               # end values in a temporary array.
+                               my @values = &Guardian::Base::IPOrNet2Int($_);
+
+                               # If the function returned any values, the line contained a valid
+                               # single address or network which successfully has been converted into
+                               # binary format.
+                               if (@values) {
+                                       # Assign the array as value to the ignorehash.
+                                       $ignorehash{$_} = [@values];
+                               } else {
+                                       # Log invalid entry.
+                                       $logger->Log("err", "$file contains an invalid address/network: $_");
+
+                                       # Skip line.
+                                       next;
+                               }
+                       }
+
+                       # Close filehandle.
+                       close(INCLUDE);
+               }
+       }
+
        # Get amount of current elements in hash.
        my $amount = scalar(keys(%ignorehash));
 
index dbd3a815dfed5badbf827e8b004a7cde6d57483a..2214e477e10d2cdd0c29b40337537d081debf000 100644 (file)
@@ -19,6 +19,7 @@ my %loglevels = (
 # This hash contains the supported log facilities and their corresponding subroutines.
 my %logfacilities = (
        "console" => \&LogFacilityConsole,
+       "file" => \&LogFacilityFile,
        "syslog" => \&LogFacilitySyslog,
 );
 
@@ -81,7 +82,7 @@ sub Log ($$) {
                my $use_facility = $self->{LogFacility};
 
                # Transmit log message to the correct log facility.
-               $logfacilities{$use_facility}->($level, $message);
+               $logfacilities{$use_facility}->($self, $level, $message);
        }
 }
 
@@ -103,6 +104,7 @@ sub GetLogLevels () {
 ## message to STDOUT.
 #
 sub LogFacilityConsole ($$) {
+       my $self = shift;
        my ($type, $message) = @_;
 
        # Print message on STDOUT.
@@ -115,6 +117,7 @@ sub LogFacilityConsole ($$) {
 ## This log facility sends a given log message to the system log service (syslog).
 #
 sub LogFacilitySyslog ($$) {
+       my $self = shift;
        my ($type, $message) = @_;
 
        # The syslog function works best with an array based input,
@@ -131,4 +134,23 @@ sub LogFacilitySyslog ($$) {
        closelog();
 }
 
+#
+## LogFacilityFile function.
+#
+## This log facility will write any given log messages to a specified log file.
+#
+sub LogFacilityFile ($$) {
+       my $self = shift;
+       my ($type, $message) = @_;
+
+       # Open the logfile for writing.
+       open(LOGFILE, '>>', $self->{LogFile}) or die "Could not write to $self->{LogFile}: $!\n";
+
+       # Write log message to file.
+       print LOGFILE "\[$type\] $message\n";
+
+       # Close filehandle.
+       close(FILE);
+}
+
 1;
index db54cf35f95f2cae7910556ffe297d70df844d50..255e6cc3012aa80e54df13639f2f5d02794e8a07 100644 (file)
@@ -9,7 +9,9 @@ our @EXPORT_OK = qw(IsSupportedParser Parser);
 # This hash contains all supported parsers and which function
 # has to be called to parse messages in the right way.
 my %logfile_parsers = (
+       "httpd" => \&message_parser_httpd,
        "snort" => \&message_parser_snort,
+       "ssh" => \&message_parser_ssh,
 );
 
 #
@@ -28,18 +30,16 @@ sub Parser ($$) {
        }
 
        # Call responsible message parser.
-       my $action = $logfile_parsers{$parser}->(@message);
+       my @actions = $logfile_parsers{$parser}->(@message);
 
-       # If the parser successfully parsed the message, an action
-       # has been returned.
-       if ($action) {
-               # Return which action should be performed.
-               return "count $action";
+       # In case an action has been returned, return it too. 
+       if (@actions) {
+               # Return which actions should be performed.
+               return @actions;
        }
 
-       # If the parser was not able to parse the the given message
-       # in the right way, return Nothing.
-       return;
+       # Return undef, if no actions are required.
+       return undef;
 }
 
 #
@@ -125,4 +125,119 @@ sub message_parser_snort(@) {
        return;
 }
 
+#
+## The SSH message parser.
+#
+## This subfunction is used for parsing and detecting different attacks
+## against the SSH service.
+#
+sub message_parser_ssh (@) {
+       my @message = @_;
+       my @actions;
+
+       # The name of the parser module.
+       my $name = "SSH";
+
+       # Variable to store the grabbed IP-address.
+       my $address;
+
+       # Variable to store the parsed event.
+       my $message;
+
+       # Loop through all lines, in case multiple one have
+       # been passed.
+       foreach my $line (@message) {
+               # Check for failed password attempts.
+               if ($line =~/.*sshd.*Failed password for (.*) from (.*) port.*/) {
+                       # Store the grabbed IP-address.
+                       $address = $2;
+
+                       # Set event message.
+                       $message = "Possible SSH-Bruteforce Attack for user: $1.";
+               }
+
+               # This should catch Bruteforce Attacks with enabled preauth
+               elsif ($line =~ /.*sshd.*Received disconnect from (.*):.*\[preauth\]/) {
+                       # Store obtained IP-address.
+                       $address = $1;
+
+                       # Set event message.
+                       $message = "Possible SSH-Bruteforce Attack - failed preauth.";
+               }
+
+               # Check if at least the IP-address information has been extracted.
+               if (defined ($address)) {
+                       # Add the extracted values and event message for the computed
+                       # event to the actions array.
+                       push(@actions, "count $address $name $message");
+               }
+       }
+
+       # If any actions are required, return the array.
+       if (@actions) {
+               return (@actions);
+       }
+
+       # If we got here, the provided message is not affected by any filter and
+       # therefore can be skipped. Return nothing (False) in this case.
+       return;
+}
+
+#
+## The HTTPD message parser.
+#
+## This subfunction is used for parsing and detecting different attacks
+## against a running HTTPD service.
+#
+sub message_parser_httpd (@) {
+       my @message = @_;
+       my @actions;
+
+       # The name of the parser module.
+       my $name = "HTTPD";
+
+       # Variable to store the grabbed IP-address.
+       my $address;
+
+       # Variable to store the parsed event.
+       my $message;
+
+       # Loop through all lines, in case multiple one have
+       # been passed.
+       foreach my $line (@message) {
+               # This will catch brute-force attacks against htaccess logins (username).
+               if ($line =~ /.*\[error\] \[client (.*)\] user(.*) not found:.*/) {
+                       # Store the grabbed IP-address.
+                       $address = $1;
+
+                       # Set event message.
+                       $message = "Possible WUI brute-force attack, wrong user: $2.";
+               }
+
+               # Detect htaccess password brute-forcing against a username.
+               elsif ($line =~ /.*\[error\] \[client (.*)\] user(.*): authentication failure for.*/) {
+                       # Store the extracted IP-address.
+                       $address = $1;
+
+                       # Set event message.
+                       $message = "Possible WUI brute-force attack, wrong password for user: $2.";
+               }
+
+               # Check if at least the IP-address information has been extracted.
+               if (defined ($address)) {
+                       # Add the extracted values and event message to the actions array.
+                       push(@actions, "count $address $name $message");
+               }
+       }
+
+       # If any actions are required, return the array.
+       if (@actions) {
+               return @actions;
+       }
+
+       # If we got here, the provided message is not affected by any filter and
+       # therefore can be skipped. Return nothing (False) in this case.
+       return;
+}
+
 1;
index 9c1985c924bc253a5a2af06e918a9a3f352258eb..d4c9a3051ab9b16ec6d72de39ce81268868b7247 100644 (file)
@@ -19,6 +19,7 @@ my %supported_commands = (
        'unblock' => 'unblock',
        'flush' => 'flush',
        'reload' => 'reload',
+       'reload-ignore-list' => 'reload-ignore-list',
        'logrotate' => 'logrotate',
 );
 
@@ -29,7 +30,9 @@ my %supported_commands = (
 ## mechanism for guardian.  The server function creates an UNIX
 ## socket.
 #
-sub Server () {
+sub Server ($) {
+       my $socket_owner = shift;
+
        # If the path for the socketfile does not exist, try to
        # create it.
        unless (-d "$socketpath") {
@@ -49,6 +52,22 @@ sub Server () {
                Type => SOCK_STREAM,
        ) or die "Could not create socket: $!";
 
+       
+       # Translate the given user/group name into ID values.
+       if (defined ($socket_owner)) {
+               # Splitt provided user/group into single arguments.
+               my ($username, $groupname) = split(/:/, $socket_owner);
+
+               # Get the ID for the given user name.
+               my $uid = getpwnam($username) or die "Could not get an UID for $username: $!";
+
+               # Get the ID for given group name.
+               my $gid = getgrnam($groupname) or die "Could not get a GID for $groupname: $!";
+
+               # Set new ownership for the socket file.
+               chown($uid, $gid, "$socketfile") or die "Could not change ownership to ($uid:$gid) for $socketfile: $!";
+       }
+
        # Return the server object.
        return $server;
 }