Mercurial > hg > maltfilter
comparison maltfilter @ 60:38885f5f34f6
Added evidence gathering functionality.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 17 Aug 2009 08:22:24 +0300 |
parents | 69c39b5c6277 |
children | 924720517cf9 |
comparison
equal
deleted
inserted
replaced
59:69c39b5c6277 | 60:38885f5f34f6 |
---|---|
7 # | 7 # |
8 ############################################################################# | 8 ############################################################################# |
9 use strict; | 9 use strict; |
10 use Date::Parse; | 10 use Date::Parse; |
11 use Net::IP; | 11 use Net::IP; |
12 | 12 use Net::DNS; |
13 my $progversion = "0.13.1"; | 13 use LWP::UserAgent; |
14 | |
15 my $progversion = "0.14.0"; | |
14 my $progbanner = | 16 my $progbanner = |
15 "Malicious Attack Livid Termination Filter daemon (maltfilter) v$progversion\n". | 17 "Malicious Attack Livid Termination Filter daemon (maltfilter) v$progversion\n". |
16 "Programmed by Matti 'ccr' Hamalainen <ccr\@tnsp.org>\n". | 18 "Programmed by Matti 'ccr' Hamalainen <ccr\@tnsp.org>\n". |
17 "(C) Copyright 2009 Tecnic Software productions (TNSP)\n"; | 19 "(C) Copyright 2009 Tecnic Software productions (TNSP)\n"; |
18 | 20 |
27 "TRESHOLD" => 3, | 29 "TRESHOLD" => 3, |
28 "ACTION" => "DROP", | 30 "ACTION" => "DROP", |
29 "LOGFILE" => "", | 31 "LOGFILE" => "", |
30 "IPTABLES" => "/sbin/iptables", | 32 "IPTABLES" => "/sbin/iptables", |
31 | 33 |
32 "STATUS_FILE_PLAIN" => "", | 34 "FULL_TIME" => 1, |
33 "STATUS_FILE_HTML" => "", | 35 "STATUS_FILE_PLAIN" => "", |
34 "STATUS_FILE_CSS" => "", | 36 "STATUS_FILE_HTML" => "", |
35 | 37 "STATUS_FILE_CSS" => "", |
36 "WHOIS_URL" => "http://whois.domaintools.com/", | 38 "WHOIS_URL" => "http://whois.domaintools.com/", |
37 | 39 |
38 "CHK_SSHD" => 1, | 40 "CHK_SSHD" => 1, |
39 "CHK_KNOWN_CGI" => 1, | 41 "CHK_KNOWN_CGI" => 1, |
40 "CHK_PHP_XSS" => 1, | 42 "CHK_PHP_XSS" => 1, |
41 "CHK_PROXY_SCAN" => 1, | 43 "CHK_PROXY_SCAN" => 1, |
42 "CHK_ROOT_SSH_PWD" => 0, | 44 "CHK_ROOT_SSH_PWD" => 0, |
43 "CHK_SYSACCT_SSH_PWD" => 0, | 45 "CHK_SYSACCT_SSH_PWD" => 0, |
44 "CHK_GOOD_HOSTS" => "", | 46 "CHK_GOOD_HOSTS" => "", |
45 | 47 |
48 "PASSWD" => "/etc/passwd", | |
46 "SYSACCT_MIN_UID" => 1, | 49 "SYSACCT_MIN_UID" => 1, |
47 "SYSACCT_MAX_UID" => 100, | 50 "SYSACCT_MAX_UID" => 100, |
48 | 51 |
49 "FULL_TIME" => 1, | 52 "EVIDENCE" => 0, |
50 | 53 "EVIDENCE_DIR" => "", |
51 "PASSWD" => "/etc/passwd", | |
52 ); | 54 ); |
53 | 55 |
54 my @noblock_ips_def = ( | 56 my @noblock_ips_def = ( |
55 "127.0.0.1", | 57 "127.0.0.1", |
56 ); | 58 ); |
102 my $merr = $3; | 104 my $merr = $3; |
103 | 105 |
104 # (3.1) Simple match for generic PHP XSS vulnerability scans | 106 # (3.1) Simple match for generic PHP XSS vulnerability scans |
105 if ($merr =~ /\.php\?\S*?=http:\/\/([^\/]+)/) { | 107 if ($merr =~ /\.php\?\S*?=http:\/\/([^\/]+)/) { |
106 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { | 108 if (!check_hosts($settings{"CHK_GOOD_HOSTS"}, $1)) { |
109 if ($merr =~ /\.php\?\S*?=(http:\/\/[^\&\?]+\??)/) { | |
110 check_add_evidence($mip, $1); | |
111 } | |
107 check_add_hit($mip, $mdate, "PHP XSS", $merr, $settings{"CHK_PHP_XSS"}); | 112 check_add_hit($mip, $mdate, "PHP XSS", $merr, $settings{"CHK_PHP_XSS"}); |
108 } | 113 } |
109 } | 114 } |
110 # (3.2) Try to match proxy scanning attempts | 115 # (3.2) Try to match proxy scanning attempts |
111 elsif ($merr =~ /^http:\/\/([^\/]+)/) { | 116 elsif ($merr =~ /^http:\/\/([^\/]+)/) { |
407 printP($m, $f, bb($m).$nkeys.eb($m)." entries total, ".bb($m).$nhits.eb($m)." hits total.\n"); | 412 printP($m, $f, bb($m).$nkeys.eb($m)." entries total, ".bb($m).$nhits.eb($m)." hits total.\n"); |
408 } | 413 } |
409 | 414 |
410 sub cmp_hits($$$) | 415 sub cmp_hits($$$) |
411 { | 416 { |
412 return $_[0]->{$_[2]}{"hits"} <=> $_[0]->{$_[1]}{"hits"}; | 417 my $s1 = $_[0]->{$_[1]}; |
418 my $s2 = $_[0]->{$_[2]}; | |
419 | |
420 return -1 if ($s2->{"date2"} < $s1->{"date2"}); | |
421 return 1 if ($s2->{"date2"} > $s1->{"date2"}); | |
422 return $s2->{"hits"} <=> $s1->{"hits"}; | |
413 } | 423 } |
414 | 424 |
415 sub get_period($) | 425 sub get_period($) |
416 { | 426 { |
417 my ($str, $r, $k); | 427 my ($str, $r, $k); |
487 close(STATUS); | 497 close(STATUS); |
488 } | 498 } |
489 | 499 |
490 | 500 |
491 ############################################################################# | 501 ############################################################################# |
502 ### Evidence gathering | |
503 ############################################################################# | |
504 my %evidence = (); | |
505 | |
506 sub check_add_evidence($$) | |
507 { | |
508 my ($mip, $mdata) = @_; | |
509 | |
510 return unless ($settings{"EVIDENCE"}); | |
511 | |
512 my $tmp = $mdata; | |
513 $tmp =~ s/http:\/\///; | |
514 $tmp =~ s/^\.+/_/; | |
515 $tmp =~ s/[^A-Za-z0-9:\.]/_/g; | |
516 | |
517 $evidence{$mdata}{"coll"} = $tmp; | |
518 $evidence{$mdata}{"hosts"}{$mip} = 1; | |
519 } | |
520 | |
521 sub http_fetch($$) | |
522 { | |
523 my $tmp = LWP::UserAgent->new; | |
524 $tmp->agent("-"); | |
525 $tmp->timeout(10); | |
526 $tmp->default_headers->referer($_[1]); | |
527 my $req = HTTP::Request->new(GET => $_[0]); | |
528 return $tmp->request($req); | |
529 } | |
530 | |
531 sub gather_evidence | |
532 { | |
533 my $dns = Net::DNS::Resolver->new; | |
534 my $base = $settings{"EVIDENCE_DIR"}; | |
535 | |
536 return unless ($settings{"EVIDENCE"}); | |
537 | |
538 mdie("Evidence directory '$base' has disappeared.\n") unless (-e $base); | |
539 | |
540 foreach my $url (keys %evidence) { | |
541 if (!$evidence{$url}{"done"}) { | |
542 my $filename = $base."/".$evidence{$url}{"coll"}.".data"; | |
543 my $filename2 = $base."/".$evidence{$url}{"coll"}.".hosts"; | |
544 my $code = 0; | |
545 my $message = ""; | |
546 | |
547 # Get data contents only once | |
548 if (! -e $filename) { | |
549 mlog(1, "Fetching evidence for $url\n"); | |
550 my $res = http_fetch($url, ""); | |
551 $code = $res->code; | |
552 $message = $res->message; | |
553 open(FILE, ">:raw", $filename) or mdie("Could not open '$filename' for writing.\n"); | |
554 binmode(FILE, ":raw"); | |
555 if ($res->code >= 200 && $res->code <= 201) { | |
556 print FILE $res->content; | |
557 } else { | |
558 print FILE "[$code] $message\n"; | |
559 } | |
560 close(FILE); | |
561 } | |
562 | |
563 # Check if we are appending hosts to existing data | |
564 if (-e $filename2) { | |
565 open(FILE, "<", $filename2) or mdie("Could not open '$filename2' for reading.\n"); | |
566 while (<FILE>) { | |
567 if (/^(\d+\.\d+\.\d+\.\d+) *\|/) { | |
568 if (defined($evidence{$url}{"hosts"}{$1})) { | |
569 delete($evidence{$url}{"hosts"}{$1}); | |
570 } | |
571 } | |
572 } | |
573 close(FILE); | |
574 open(FILE, ">>", $filename2) or mdie("Could not open '$filename2' for appending.\n"); | |
575 } else { | |
576 open(FILE, ">", $filename2) or mdie("Could not open '$filename2' for writing.\n"); | |
577 print FILE "# HTTP request result: [$code] $message\n"; | |
578 print FILE "# Hosts which requested $url\n\n"; | |
579 } | |
580 | |
581 foreach my $host (sort keys %{$evidence{$url}{"hosts"}}) { | |
582 print "lol: $host\n"; | |
583 my $query = $dns->search($host); | |
584 my @names = (); | |
585 undef(@names); | |
586 if ($query) { | |
587 foreach my $rr ($query->answer) { | |
588 push(@names, $rr->{"ptrdname"}) if defined($rr->{"ptrdname"}); | |
589 } | |
590 } | |
591 printf FILE "%-15s | %s\n", $host, join(" | ", @names); | |
592 } | |
593 close(FILE); | |
594 | |
595 delete($evidence{$url}); | |
596 | |
597 return unless $reportmode; | |
598 } | |
599 } | |
600 } | |
601 | |
602 | |
603 ############################################################################# | |
492 ### Entry management / handling functions | 604 ### Entry management / handling functions |
493 ############################################################################# | 605 ############################################################################# |
494 ### Check if given IP or host exists in array | 606 ### Check if given IP or host exists in array |
495 sub check_hosts_array($$) | 607 sub check_hosts_array($$) |
496 { | 608 { |
531 ### Get current Netfilter INPUT table entries that match | 643 ### Get current Netfilter INPUT table entries that match |
532 ### entry types we manage, e.g. blocklist | 644 ### entry types we manage, e.g. blocklist |
533 sub update_blocklist($) | 645 sub update_blocklist($) |
534 { | 646 { |
535 # NOTICE: argument not used now | 647 # NOTICE: argument not used now |
648 my $first = $_[0]; | |
536 | 649 |
537 $ENV{"PATH"} = ""; | 650 $ENV{"PATH"} = ""; |
538 open(STATUS, $settings{"IPTABLES"}." -v -n -L INPUT |") or | 651 open(STATUS, $settings{"IPTABLES"}." -v -n -L INPUT |") or |
539 mdie("Could not execute ".$settings{"IPTABLES"}."\n"); | 652 mdie("Could not execute ".$settings{"IPTABLES"}."\n"); |
540 my %newlist = (); | 653 my %newlist = (); |
543 chomp; | 656 chomp; |
544 if (/^\s*(\d+)\s+\d+\s+$settings{"ACTION"}\s+all\s+--\s+\*\s+\*\s+(\d+\.\d+\.\d+\.\d+)\s+0\.0\.0\.0\/0\s*$/) { | 657 if (/^\s*(\d+)\s+\d+\s+$settings{"ACTION"}\s+all\s+--\s+\*\s+\*\s+(\d+\.\d+\.\d+\.\d+)\s+0\.0\.0\.0\/0\s*$/) { |
545 my $mip = $2; | 658 my $mip = $2; |
546 my $mdate = time(); | 659 my $mdate = time(); |
547 if (!defined($blocklist{$mip})) { | 660 if (!defined($blocklist{$mip})) { |
548 mlog(2, "* $mip appeared in iptables.\n"); | 661 mlog(2, "* $mip appeared in iptables.\n") if ($first > 0); |
549 $blocklist{$2} = $mdate; | 662 $blocklist{$2} = $mdate; |
550 } | 663 } |
551 $newlist{$2} = $mdate; | 664 $newlist{$2} = $mdate; |
552 update_entry(\%statlist, $mip, -1, "IPTABLES", "", 0); | 665 update_entry(\%statlist, $mip, -1, "IPTABLES", "", 0); |
553 } | 666 } |
832 $counter = 0; | 945 $counter = 0; |
833 update_blocklist(time()); | 946 update_blocklist(time()); |
834 weed_entries(); | 947 weed_entries(); |
835 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); | 948 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); |
836 generate_status($settings{"STATUS_FILE_HTML"}, 1); | 949 generate_status($settings{"STATUS_FILE_HTML"}, 1); |
837 } | 950 gather_evidence(); |
838 sleep(5); | 951 } |
952 sleep(2); | |
839 foreach my $filename (keys %filehandles) { | 953 foreach my $filename (keys %filehandles) { |
840 seek($filehandles{$filename}, $filepos{$filename}, 0); | 954 seek($filehandles{$filename}, $filepos{$filename}, 0); |
841 } | 955 } |
842 } | 956 } |
843 } | 957 } |
930 # Test existence of iptables | 1044 # Test existence of iptables |
931 if (! -e $settings{"IPTABLES"} || ! -x $settings{"IPTABLES"}) { | 1045 if (! -e $settings{"IPTABLES"} || ! -x $settings{"IPTABLES"}) { |
932 mdie("iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n"); | 1046 mdie("iptables binary does not exist or is not executable: ".$settings{"IPTABLES"}."\n"); |
933 } | 1047 } |
934 | 1048 |
1049 # Check evidence settings | |
1050 if ($settings{"EVIDENCE"}) { | |
1051 my $base = $settings{"EVIDENCE_DIR"}; | |
1052 mdie("Evidence directory (EVIDENCE_DIR) not set in configuration.\n") if ($base eq ""); | |
1053 mdie("Evidence directory '$base' does not exist.\n") unless (-e $base); | |
1054 mdie("Path '$base' is not a directory.\n") unless (-d $base); | |
1055 mdie("Evidence directory '$base' is not writable by euid.\n") unless (-w $base); | |
1056 } | |
1057 | |
935 # Check settings | 1058 # Check settings |
936 mdie("SYSACCT_MIN_UID must be >= 1.\n") unless ($settings{"SYSACCT_MIN_UID"} >= 1); | 1059 mdie("SYSACCT_MIN_UID must be >= 1.\n") unless ($settings{"SYSACCT_MIN_UID"} >= 1); |
937 mdie("SYSACCT_MAX_UID must be >= SYSACCT_MIN_UID.\n") unless ($settings{"SYSACCT_MAX_UID"} >= $settings{"SYSACCT_MIN_UID"}); | 1060 mdie("SYSACCT_MAX_UID must be >= SYSACCT_MIN_UID.\n") unless ($settings{"SYSACCT_MAX_UID"} >= $settings{"SYSACCT_MIN_UID"}); |
938 | 1061 |
939 open(PASSWD, "<", $settings{"PASSWD"}) or mdie("Could not open '".$settings{"PASSWD"}."' for reading!\n"); | 1062 open(PASSWD, "<", $settings{"PASSWD"}) or mdie("Could not open '".$settings{"PASSWD"}."' for reading!\n"); |
1003 if ($settings{"DRY_RUN"}) { | 1126 if ($settings{"DRY_RUN"}) { |
1004 if ($reportmode) { | 1127 if ($reportmode) { |
1005 mlog(-1, "Outputting report files.\n"); | 1128 mlog(-1, "Outputting report files.\n"); |
1006 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); | 1129 generate_status($settings{"STATUS_FILE_PLAIN"}, 0); |
1007 generate_status($settings{"STATUS_FILE_HTML"}, 1); | 1130 generate_status($settings{"STATUS_FILE_HTML"}, 1); |
1131 gather_evidence(); | |
1008 malt_cleanup(); | 1132 malt_cleanup(); |
1009 } else { | 1133 } else { |
1010 malt_scan(); | 1134 malt_scan(); |
1011 malt_cleanup(); | 1135 malt_cleanup(); |
1012 } | 1136 } |