changeset 60:38885f5f34f6

Added evidence gathering functionality.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 17 Aug 2009 08:22:24 +0300
parents 69c39b5c6277
children 8b33436dd18b
files maltfilter
diffstat 1 files changed, 136 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/maltfilter	Mon Aug 17 03:58:41 2009 +0300
+++ b/maltfilter	Mon Aug 17 08:22:24 2009 +0300
@@ -9,8 +9,10 @@
 use strict;
 use Date::Parse;
 use Net::IP;
+use Net::DNS;
+use LWP::UserAgent;
 
-my $progversion = "0.13.1";
+my $progversion = "0.14.0";
 my $progbanner =
 "Malicious Attack Livid Termination Filter daemon (maltfilter) v$progversion\n".
 "Programmed by Matti 'ccr' Hamalainen <ccr\@tnsp.org>\n".
@@ -29,11 +31,11 @@
   "LOGFILE" => "",
   "IPTABLES" => "/sbin/iptables",
 
-  "STATUS_FILE_PLAIN" => "",
-  "STATUS_FILE_HTML" => "",
-  "STATUS_FILE_CSS" => "",
-
-  "WHOIS_URL" => "http://whois.domaintools.com/",
+  "FULL_TIME"           => 1,
+  "STATUS_FILE_PLAIN"   => "",
+  "STATUS_FILE_HTML"    => "",
+  "STATUS_FILE_CSS"     => "",
+  "WHOIS_URL"           => "http://whois.domaintools.com/",
 
   "CHK_SSHD"            => 1,
   "CHK_KNOWN_CGI"       => 1,
@@ -43,12 +45,12 @@
   "CHK_SYSACCT_SSH_PWD" => 0,
   "CHK_GOOD_HOSTS"      => "",
 
+  "PASSWD"              => "/etc/passwd",
   "SYSACCT_MIN_UID"     => 1,
   "SYSACCT_MAX_UID"     => 100,
 
-  "FULL_TIME"           => 1,
-
-  "PASSWD"              => "/etc/passwd",
+  "EVIDENCE"            => 0,
+  "EVIDENCE_DIR"        => "",
 );
 
 my @noblock_ips_def = (
@@ -104,6 +106,9 @@
     # (3.1) Simple match for generic PHP XSS vulnerability scans
     if ($merr =~ /\.php\?\S*?=http:\/\/([^\/]+)/) {
       if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) {
+        if ($merr =~ /\.php\?\S*?=(http:\/\/[^\&\?]+\??)/) {
+          check_add_evidence($mip, $1);
+        }
         check_add_hit($mip, $mdate, "PHP XSS", $merr, $settings{"CHK_PHP_XSS"});
       }
     }
@@ -409,7 +414,12 @@
 
 sub cmp_hits($$$)
 {
-  return $_[0]->{$_[2]}{"hits"} <=> $_[0]->{$_[1]}{"hits"};
+  my $s1 = $_[0]->{$_[1]};
+  my $s2 = $_[0]->{$_[2]};
+
+  return -1 if ($s2->{"date2"} < $s1->{"date2"});
+  return  1 if ($s2->{"date2"} > $s1->{"date2"});
+  return $s2->{"hits"} <=> $s1->{"hits"};
 }
 
 sub get_period($)
@@ -489,6 +499,108 @@
 
 
 #############################################################################
+### Evidence gathering
+#############################################################################
+my %evidence = ();
+
+sub check_add_evidence($$)
+{
+  my ($mip, $mdata) = @_;
+
+  return unless ($settings{"EVIDENCE"});
+
+  my $tmp = $mdata;
+  $tmp =~ s/http:\/\///;
+  $tmp =~ s/^\.+/_/;
+  $tmp =~ s/[^A-Za-z0-9:\.]/_/g;
+
+  $evidence{$mdata}{"coll"} = $tmp;
+  $evidence{$mdata}{"hosts"}{$mip} = 1;
+}
+
+sub http_fetch($$)
+{
+  my $tmp = LWP::UserAgent->new;
+  $tmp->agent("-");
+  $tmp->timeout(10);
+  $tmp->default_headers->referer($_[1]);
+  my $req = HTTP::Request->new(GET => $_[0]);
+  return $tmp->request($req);
+}
+
+sub gather_evidence
+{
+  my $dns = Net::DNS::Resolver->new;
+  my $base = $settings{"EVIDENCE_DIR"};
+
+  return unless ($settings{"EVIDENCE"});
+
+  mdie("Evidence directory '$base' has disappeared.\n") unless (-e $base);
+  
+  foreach my $url (keys %evidence) {
+    if (!$evidence{$url}{"done"}) {
+      my $filename = $base."/".$evidence{$url}{"coll"}.".data";
+      my $filename2 = $base."/".$evidence{$url}{"coll"}.".hosts";
+      my $code = 0;
+      my $message = "";
+
+      # Get data contents only once
+      if (! -e $filename) {
+        mlog(1, "Fetching evidence for $url\n");
+        my $res = http_fetch($url, "");
+        $code = $res->code;
+        $message = $res->message;
+        open(FILE, ">:raw", $filename) or mdie("Could not open '$filename' for writing.\n");
+        binmode(FILE, ":raw");
+        if ($res->code >= 200 && $res->code <= 201) {
+          print FILE $res->content;
+        } else {
+          print FILE "[$code] $message\n";
+        }
+        close(FILE);
+      }
+
+      # Check if we are appending hosts to existing data
+      if (-e $filename2) {
+        open(FILE, "<", $filename2) or mdie("Could not open '$filename2' for reading.\n");
+        while (<FILE>) {
+          if (/^(\d+\.\d+\.\d+\.\d+) *\|/) {
+            if (defined($evidence{$url}{"hosts"}{$1})) {
+              delete($evidence{$url}{"hosts"}{$1});
+            }
+          }
+        }
+        close(FILE);
+        open(FILE, ">>", $filename2) or mdie("Could not open '$filename2' for appending.\n");
+      } else {
+        open(FILE, ">", $filename2) or mdie("Could not open '$filename2' for writing.\n");
+        print FILE "# HTTP request result: [$code] $message\n";
+        print FILE "# Hosts which requested $url\n\n";
+      }
+
+      foreach my $host (sort keys %{$evidence{$url}{"hosts"}}) {
+        print "lol: $host\n";
+        my $query = $dns->search($host);
+        my @names = ();
+        undef(@names);
+        if ($query) {
+          foreach my $rr ($query->answer) {
+            push(@names, $rr->{"ptrdname"}) if defined($rr->{"ptrdname"});
+          }
+        }
+        printf FILE "%-15s | %s\n", $host, join(" | ", @names);
+      }
+      close(FILE);
+
+      delete($evidence{$url});
+
+      return unless $reportmode;
+    }
+  }
+}
+
+
+#############################################################################
 ### Entry management / handling functions
 #############################################################################
 ### Check if given IP or host exists in array
@@ -533,6 +645,7 @@
 sub update_blocklist($)
 {
   # NOTICE: argument not used now
+  my $first = $_[0];
   
   $ENV{"PATH"} = "";
   open(STATUS, $settings{"IPTABLES"}." -v -n -L INPUT |") or
@@ -545,7 +658,7 @@
       my $mip = $2;
       my $mdate = time();
       if (!defined($blocklist{$mip})) {
-        mlog(2, "* $mip appeared in iptables.\n");
+        mlog(2, "* $mip appeared in iptables.\n") if ($first > 0);
         $blocklist{$2} = $mdate;
       }
       $newlist{$2} = $mdate;
@@ -834,8 +947,9 @@
       weed_entries();
       generate_status($settings{"STATUS_FILE_PLAIN"}, 0);
       generate_status($settings{"STATUS_FILE_HTML"}, 1);
+      gather_evidence();
     }
-    sleep(5);
+    sleep(2);
     foreach my $filename (keys %filehandles) {
       seek($filehandles{$filename}, $filepos{$filename}, 0);
     }
@@ -932,6 +1046,15 @@
     mdie("iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n");
   }
 
+  # Check evidence settings
+  if ($settings{"EVIDENCE"}) {
+    my $base = $settings{"EVIDENCE_DIR"};
+    mdie("Evidence directory (EVIDENCE_DIR) not set in configuration.\n") if ($base eq "");
+    mdie("Evidence directory '$base' does not exist.\n") unless (-e $base);
+    mdie("Path '$base' is not a directory.\n") unless (-d $base);
+    mdie("Evidence directory '$base' is not writable by euid.\n") unless (-w $base);
+  }
+  
   # Check settings
   mdie("SYSACCT_MIN_UID must be >= 1.\n") unless ($settings{"SYSACCT_MIN_UID"} >= 1);
   mdie("SYSACCT_MAX_UID must be >= SYSACCT_MIN_UID.\n") unless ($settings{"SYSACCT_MAX_UID"} >= $settings{"SYSACCT_MIN_UID"});
@@ -1005,6 +1128,7 @@
     mlog(-1, "Outputting report files.\n");
     generate_status($settings{"STATUS_FILE_PLAIN"}, 0);
     generate_status($settings{"STATUS_FILE_HTML"}, 1);
+    gather_evidence();
     malt_cleanup();
   } else {
     malt_scan();