# HG changeset patch # User Matti Hamalainen # Date 1250486544 -10800 # Node ID 38885f5f34f66f362450d97fa6d3a33b2145f594 # Parent 69c39b5c627753b02bb262c932119a8fab2b358d Added evidence gathering functionality. diff -r 69c39b5c6277 -r 38885f5f34f6 maltfilter --- 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 \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 () { + 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();