changeset 39:d96229159abc

v0.11.0: More fixes; Configuration files are now re-read when HUP signal is received; Multiple configuration files feature actually works now.
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 16 Aug 2009 01:30:13 +0300
parents e1e28a76317f
children 24babaa1e331
files README maltfilter
diffstat 2 files changed, 64 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/README	Sun Aug 16 00:54:21 2009 +0300
+++ b/README	Sun Aug 16 01:30:13 2009 +0300
@@ -1,4 +1,4 @@
-Malicious Attack Livid Termination Filter daemon (maltfilter) v0.10.4
+Malicious Attack Livid Termination Filter daemon (maltfilter) v0.11.0
 =====================================================================
 Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org>
 (C) Copyright 2009 Tecnic Software productions (TNSP)
--- a/maltfilter	Sun Aug 16 00:54:21 2009 +0300
+++ b/maltfilter	Sun Aug 16 01:30:13 2009 +0300
@@ -10,7 +10,7 @@
 use Date::Parse;
 use Net::IP;
 
-my $progversion = "0.10.4";
+my $progversion = "0.11.0";
 my $progbanner =
 "Malicious Attack Livid Termination Filter daemon (maltfilter) v$progversion\n".
 "Programmed by Matti 'ccr' Hamalainen <ccr\@tnsp.org>\n".
@@ -40,6 +40,7 @@
   "CHK_PHP_XSS"         => 1,
   "CHK_PROXY_SCAN"      => 1,
   "CHK_ROOT_SSH_PWD"    => 0,
+  "CHK_SYSACCT_SSH_PWD" => 0,
   "CHK_GOOD_HOSTS"      => "",
 );
 
@@ -62,7 +63,9 @@
 my @noblock_ips = ();    # IPs not to block
 my %filehandles = ();    # Global hash holding opened scanned log filehandles
 my $pid_file = "";       # Name of Maltfilter daemon pid file
+my @configfiles = ();    # Array of configuration file names
 my $LOGFILE;             # Maltfilter logfile handle
+my %systemacct = ();
 
 # IPs currently blocked in Netfilter $blocklist{$ip} = date
 my %blocklist = ();      
@@ -93,15 +96,26 @@
     my $merr = $2;
     
     # (1.1) Generic login scan attempts
-    if ($merr =~ /^Failed password for invalid user \S+ from (\d+\.\d+\.\d+\.\d+)/) {
-      check_add_hit($1, $mdate, "SSH login scan", "", $settings{"CHK_SSHD"});
+    if ($merr =~ /^Failed password for invalid user (\S+) from (\d+\.\d+\.\d+\.\d+)/) {
+      check_add_hit($2, $mdate, "SSH login scan", $1, $settings{"CHK_SSHD"});
     }
-    # (1.2) Root SSH login password bruteforcing attempts
+    # (1.2) Root account SSH login password bruteforcing attempts.
     # NOTICE! Do not enable this setting, if you allow SSH root logins via
-    # password authentication! Mistyping password may get you blocked then. :)
+    # password authentication! Mistyping password may get you blocked unless
+    # your host IP is defined in NOBLOCK_IPS!
     elsif (/^Failed password for root from (\d+\.\d+\.\d+\.\d+)/) {
       check_add_hit($1, $mdate, "Root SSH password bruteforce", "", $settings{"CHK_ROOT_SSH_PWD"});
     }
+    # (1.3) System account SSH login password bruteforcing attempts.
+    # NOTICE! If you enable this setting, make sure have defined safe
+    # host IPs in NOBLOCK_IPS, and that your system DOES NOT have passwords
+    # for system accounts (UID < 100) .. which would be stupid anyway.
+    if ($merr =~ /^Failed password for (\S+) from (\d+\.\d+\.\d+\.\d+)/) {
+      my $mip = $2; my $macct = $1;
+      if (defined($systemacct{$macct})) {
+        check_add_hit($mip, $mdate, "SSH system account bruteforce", $macct, $settings{"CHK_SYSACCT_SSH_PWD"});
+      }
+    }
   }
   # (2) Common/known exploitable CGI/PHP software scans (like phpMyAdmin)
   # NOTICE! This matches ERRORLOG, thus it only works if you DO NOT have
@@ -380,7 +394,7 @@
   
   return unless ($filename ne "");
 
-  open(STATUS, ">", $filename) or die("Could not open '".$filename."'!\n");
+  open(STATUS, ">", $filename) or mdie("Could not open '".$filename."'!\n");
   my $f = \*STATUS;
   
   printElem($m, $f, "
@@ -473,7 +487,7 @@
 {
   $ENV{"PATH"} = "";
   open(STATUS, $settings{"IPTABLES"}." -v -n -L INPUT |") or
-    die("Could not execute ".$settings{"IPTABLES"}."\n");
+    mdie("Could not execute ".$settings{"IPTABLES"}."\n");
   my %newlist = ();
   undef(%newlist);
   while (<STATUS>) {
@@ -578,12 +592,16 @@
   if (!defined($struct->{$mip}{"date1"}) || ($mdate > 0 && $struct->{$mip}{"date1"} < 0)) {
     $struct->{$mip}{"date1"} = $mdate;
   }
-  $struct->{$mip}{"date2"} = $mdate;
+  if (!defined($struct->{$mip}{"date2"}) || $mdate > $struct->{$mip}{"date2"}) {
+    $struct->{$mip}{"date2"} = $mdate;
+  }
     
   if (!defined($struct->{$mip}{"reason"}{$mclass}{"date2"}) || ($mdate > 0 && $struct->{$mip}{"reason"}{$mclass}{"date2"} < 0)) {
     $struct->{$mip}{"reason"}{$mclass}{"date2"} = $mdate;
   }
-  $struct->{$mip}{"reason"}{$mclass}{"date2"} = $mdate;
+  if (!defined($struct->{$mip}{"reason"}{$mclass}{"date2"}) || $mdate > $struct->{$mip}{"reason"}{$mclass}{"date2"}) {
+    $struct->{$mip}{"reason"}{$mclass}{"date2"} = $mdate;
+  }
 
   return $cnt;
 }
@@ -630,7 +648,7 @@
 ### Main helper functions
 #############################################################################
 ### Print log entry
-sub mlog
+sub mlog($$)
 {
   my $level = shift;
   my $msg = shift;
@@ -641,16 +659,23 @@
   }
 }
 
+sub mdie($)
+{
+  mlog(-1, $_[0]);
+  die($_[0]);
+}
+
 ### Initialize
 sub malt_init
 {
   mlog(0, "Updating initial blocklist from netfilter.\n");
+  malt_configure();
   update_blocklist();
 
   foreach my $filename (@scanfiles) {
     local *INFILE;
     mlog(0, "Parsing ".$filename." ...\n");
-    open(INFILE, "<", $filename) or die("Could not open '".$filename."'!\n");
+    open(INFILE, "<", $filename) or mdie("Could not open '".$filename."'!\n");
     $filehandles{$filename} = *INFILE;
     while (<INFILE>) {
       chomp;
@@ -702,6 +727,7 @@
 {
   mlog(-1, "Received HUP, reinitializing.\n");
   malt_cleanup();
+  malt_configure();
   malt_init();
   mlog(-1, "Reinitialization finished, resuming scanning.\n");
 }
@@ -714,7 +740,9 @@
   while (1) {
     my %filepos = ();
     foreach my $filename (keys %filehandles) {
-      for ($filepos{$filename} = tell($filehandles{$filename}); $_ = <$filehandles{$filename}>; $filepos{$filename} = tell($filehandles{$filename})) {
+      for ($filepos{$filename} = tell($filehandles{$filename});
+        $_ = <$filehandles{$filename}>;
+        $filepos{$filename} = tell($filehandles{$filename})) {
         chomp;
         check_log_line($_);
       }
@@ -743,7 +771,7 @@
   my $errors = 0;
   my $line = 0;
 
-  open(CONFFILE, "<", $filename) or die("Could not open configuration '".$filename."'!\n");
+  open(CONFFILE, "<", $filename) or mdie("Could not open configuration '".$filename."'!\n");
   while (<CONFFILE>) {
     $line++;
     chomp;
@@ -755,7 +783,7 @@
       if (defined($settings{$key})) {
         $settings{$key} = $value;
       } else {
-        print STDERR "[$filename:$line] Unknown setting '$key' = $value\n";
+        mlog(-1, "[$filename:$line] Unknown setting '$key' = $value\n");
         $errors = 1;
       }
     } elsif (/^\s*\"?([a-zA-Z0-9_]+)\"?\s*=>?\s*\"(.*?)\",?\s*$/) {
@@ -768,11 +796,11 @@
       } elsif (defined($settings{$key})) {
         $settings{$key} = $value;
       } else {
-        print STDERR "[$filename:$line] Unknown setting '$key' = '$value'\n";
+        mlog(-1, "[$filename:$line] Unknown setting '$key' = '$value'\n");
         $errors = 1;
       }
     } else {
-      print STDERR "[$filename:$line] Syntax error: $_\n";
+      mlog(-1, "[$filename:$line] Syntax error: $_\n");
       $errors = 1;
     }
   }
@@ -780,6 +808,18 @@
   return $errors;
 }
 
+### Read all configuration files
+sub malt_configure
+{
+  # Let user define his/her own logfiles to scan
+  mlog(0, "(Re)reading configuration files.\n");
+  @scanfiles_def = ();
+  undef(@scanfiles_def);
+  foreach my $filename (@configfiles) {
+    mdie("Errors in configuration file '$filename', bailing out.\n")
+      unless (malt_read_config($filename) == 0);
+  }
+}
 
 #############################################################################
 ###
@@ -807,20 +847,18 @@
 if ($pid_file eq "-f") {
   $reportmode = 1;
 } else {
-  die("'$pid_file' already exists, not starting.\n".
+  mdie("'$pid_file' already exists, not starting.\n".
   "If the daemon is NOT running, remove the pid-file and re-start.\n")
   if (-e $pid_file);
 }
 
 # Read configuration files
-if (defined(my $filename = shift)) {
-  # Let user define his/her own logfiles to scan
-  @scanfiles_def = ();
-  undef(@scanfiles_def);
-  die("Errors in configuration file '$filename', bailing out.\n")
-    unless (malt_read_config($filename) == 0);
+while (defined(my $filename = shift)) {
+  push(@configfiles, $filename);
 }
 
+malt_configure();
+
 # Force dry run mode if we are reporting only
 if ($reportmode) {
   $settings{"DRY_RUN"} = 1;
@@ -849,9 +887,7 @@
 
 # Test existence of iptables
 if (! -e $settings{"IPTABLES"} || ! -x $settings{"IPTABLES"}) {
-  my $msg = "iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n";
-  mlog(-1, $msg);
-  die($msg);
+  mdie("iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n");
 }
 
 mlog(-1, "Not blocking following IPs: ".join(", ", @noblock_ips)."\n");
@@ -872,7 +908,7 @@
   }
 } else {
   if (my $pid = fork) {
-    open(PIDFILE, ">", $pid_file) or die("Could not open pid file '".$pid_file."' for writing!\n");
+    open(PIDFILE, ">", $pid_file) or mdie("Could not open pid file '".$pid_file."' for writing!\n");
     print PIDFILE "$pid\n";
     close(PIDFILE);
   } else {