#!/usr/bin/perl # based on V 1.7 guardian enhanced for IPFire and snort 2.8 # Read the readme file for changes # # Enhanced for IPFire by IPFire Team # Added Portscan detection for non syslog system # Added SSH-Watch for SSH-Bruteforce Attacks # An suppected IP will be blocked on all interfaces $OS=`uname`; chomp $OS; print "OS shows $OS\n"; require 'getopts.pl'; &Getopts ('hc:d'); if (defined($opt_h)) { print "Guardian v1.7 \n"; print "guardian.pl [-hd] <-c config>\n"; print " -h shows help\n"; print " -d run in debug mode (doesn't fork, output goes to STDOUT)\n"; print " -c specifiy a configuration file other than the default (/etc/guardian.conf)\n"; exit; } &load_conf; &sig_handler_setup; print "My ip address and interface are: $hostipaddr $interface\n"; if ($hostipaddr !~ /\d+\.\d+\.\d+\.\d+/) { print "This ip address is bad : $hostipaddr\n"; die "I need a good host ipaddress\n"; } $networkaddr = $hostipaddr; $networkaddr =~ s/\d+$/0/; $gatewayaddr = `cat /var/ipfire/red/remote-ipaddress 2>/dev/null`; $broadcastaddr = $hostipaddr; $broadcastaddr =~ s/\d+$/255/; &build_ignore_hash; print "My gatewayaddess is: $gatewayaddr\n"; # This is the target hash. If a packet was destened to any of these, then the # sender of that packet will get denied, unless it is on the ignore list.. %targethash = ( "$networkaddr" => 1, "$broadcastaddr" => 1, "0" => 1, # This is what gets sent to &checkem if no # destination was found. "$hostipaddr" => 1); &get_aliases; %sshhash = (); if ( -e $targetfile ) { &load_targetfile; } if (!defined($opt_d)) { print "Becoming a daemon..\n"; &daemonize; } else { print "Running in debug mode..\n"; } open (ALERT, $alert_file) or die "can't open alert file: $alert_file: $!\n"; seek (ALERT, 0, 2); # set the position to EOF. # this is the same as a tail -f :) $counter=0; open (ALERT2, "/var/log/messages" ) or die "can't open /var/log/messages: $!\n"; seek (ALERT2, 0, 2); # set the position to EOF. # this is the same as a tail -f :) for (;;) { sleep 1; if (seek(ALERT,0,1)){ while () { chop; if (defined($opt_d)) { print "$_\n"; } if (/\[\*\*\]\s+(.*)\s+\[\*\*\]/){ $type=$1; } if (/(\d+\.\d+\.\d+\.\d+):\d+ -\> (\d+\.\d+\.\d+\.\d+):\d+/) { &checkem ($1, $2, $type); } if (/(\d+\.\d+\.\d+\.\d+)+ -\> (\d+\.\d+\.\d+\.\d+)+/) { &checkem ($1, $2, $type); } } } sleep 1; if (seek(ALERT2,0,1)){ while () { chop; if ($_=~/.*sshd.*Failed password for .* from.*/) { my @array=split(/ /,$_); my $temp = ""; if ( $array[11] eq "port" ) { $temp = $array[10]; } elsif ( $array[11] eq "from" ) { $temp = $array[12]; } else { $temp = $array[11]; } &checkssh ($temp, "possible SSH-Bruteforce Attack");} } } # Run this stuff every 30 seconds.. if ($counter == 30) { &remove_blocks; # This might get moved elsewhere, depending on how much load # it puts on the system.. &check_log_name; $counter=0; } else { $counter=$counter+1; } } sub check_log_name { my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($alert_file); if ($size < $previous_size) { # The filesize is smaller than last close (ALERT); # we checked, so we need to reopen it open (ALERT, "$alert_file"); # This should still work in our main while $previous_size=$size; # loop (I hope) write_log ("Log filename changed. Reopening $alert_file\n"); } else { $previous_size=$size; } } sub checkem { my ($source, $dest,$type) = @_; my $flag=0; return 1 if ($source eq $hostipaddr); # this should prevent is from nuking ourselves return 1 if ($source eq $gatewayaddr); # or our gateway if ($ignore{$source} == 1) { # check our ignore list.. &write_log("$source\t$type\n"); &write_log("Ignoring attack because $source is in my ignore list\n"); return 1; } # if the offending packet was sent to us, the network, or the broadcast, then if ($targethash{$dest} == 1) { &ipchain ($source, $dest, $type); } # you will see this if the destination was not in the $targethash, and the # packet was not ignored before the target check.. else { &write_log ("Odd.. source = $source, dest = $dest - No action done.\n"); if (defined ($opt_d)) { foreach $key (keys %targethash) { &write_log ("targethash{$key} = $targethash{$key}\n"); } } } } sub checkssh { my ($source,$type) = @_; my $flag=0; return 1 if ($source eq $hostipaddr); # this should prevent is from nuking ourselves return 1 if ($source eq $gatewayaddr); # or our gateway return 0 if ($sshhash{$source} > 4); # allready blocked if ( ($ignore{$source} == 1) ){ &write_log("Ignoring attack because $source is in my ignore list\n"); return 1; } if ($sshhash{$source} == 4 ) { &write_log ("source = $source, blocking for ssh attack.\n"); &ipchain ($source, "", $type); $sshhash{$source} = $sshhash{$source}+1; return 0; } if ($sshhash{$source} eq "" ){ $sshhash{$source} = 1; &write_log ("SSH Attack = $source, ssh count only $sshhash{$source} - No action done.\n"); return 0; } $sshhash{$source} = $sshhash{$source}+1; &write_log ("SSH Attack = $source, ssh count only $sshhash{$source} - No action done.\n"); } sub ipchain { my ($source, $dest, $type) = @_; &write_log ("$source\t$type\n"); if ($hash{$source} eq "") { &write_log ("Running '$blockpath $source $interface'\n"); system ("$blockpath $source $interface"); $hash{$source} = time() + $TimeLimit; } else { # We have already blocked this one, but snort detected another attack. So # we should update the time blocked.. $hash{$source} = time() + $TimeLimit; } } sub build_ignore_hash { # This would cause is to ignore all broadcasts if it # got set.. However if unset, then the attacker could spoof the packet to make # it look like it came from the network, and a reply to the spoofed packet # could be seen if the attacker were on the local network. # $ignore{$networkaddr}=1; # same thing as above, just with the broadcast instead of the network. # $ignore{$broadcastaddr}=1; my $count =0; $ignore{$gatewayaddr}=1; $ignore{$hostipaddr}=1; if ($ignorefile ne "") { open (IGNORE, $ignorefile); while () { $_=~ s/\s+$//; chomp; next if (/\#/); #skip comments next if (/^\s*$/); # and blank lines $ignore{$_}=1; $count++; } close (IGNORE); &write_log("Loaded $count addresses from $ignorefile\n"); } else { &write_log("No ignore file was loaded!\n"); } } sub load_conf { if ($opt_c eq "") { $opt_c = "/etc/guardian.conf"; } if (! -e $opt_c) { die "Need a configuration file.. please use to the -c option to name a configuration file\n"; } open (CONF, $opt_c) or die "Cannot read the config file $opt_c, $!\n"; while () { chop; next if (/^\s*$/); #skip blank lines next if (/^#/); # skip comment lines if (/LogFile\s+(.*)/) { $logfile = $1; } if (/Interface\s+(.*)/) { $interface = $1; if ( $interface eq "" ) { $interface = `cat /var/ipfire/ethernet/settings | grep RED_DEV | cut -d"=" -f2`; } } if (/AlertFile\s+(.*)/) { $alert_file = $1; } if (/IgnoreFile\s+(.*)/) { $ignorefile = $1; } if (/TargetFile\s+(.*)/) { $targetfile = $1; } if (/TimeLimit\s+(.*)/) { $TimeLimit = $1; } if (/HostIpAddr\s+(.*)/) { $hostipaddr = $1; } if (/HostGatewayByte\s+(.*)/) { $hostgatewaybyte = $1; } } if ($alert_file eq "") { print "Warning! AlertFile is undefined.. Assuming /var/log/snort.alert\n"; $alert_file="/var/log/snort.alert"; } if ($hostipaddr eq "") { print "Warning! HostIpAddr is undefined! Attempting to guess..\n"; $hostipaddr = `cat /var/ipfire/red/local-ipaddress`; print "Got it.. your HostIpAddr is $hostipaddr\n"; } if ($ignorefile eq "") { print "Warning! IgnoreFile is undefined.. going with default ignore list (hostname and gateway)!\n"; } if ($hostgatewaybyte eq "") { print "Warning! HostGatewayByte is undefined.. gateway will not be in ignore list!\n"; } if ($logfile eq "") { print "Warning! LogFile is undefined.. Assuming debug mode, output to STDOUT\n"; $opt_d = 1; } if (! -w $logfile) { print "Warning! Logfile is not writeable! Engaging debug mode, output to STDOUT\n"; $opt_d = 1; } foreach $mypath (split (/:/, $ENV{PATH})) { if (-x "$mypath/guardian_block.sh") { $blockpath = "$mypath/guardian_block.sh"; } if (-x "$mypath/guardian_unblock.sh") { $unblockpath = "$mypath/guardian_unblock.sh"; } } if ($blockpath eq "") { print "Error! Could not find guardian_block.sh. Please consult the README. \n"; exit; } if ($unblockpath eq "") { print "Warning! Could not find guardian_unblock.sh. Guardian will not be\n"; print "able to remove blocked ip addresses. Please consult the README file\n"; } if ($TimeLimit eq "") { print "Warning! Time limit not defined. Defaulting to absurdly long time limit\n"; $TimeLimit = 999999999; } } sub write_log { my $message = $_[0]; my $date = localtime(); if (defined($opt_d)) { # we are in debug mode, and not daemonized print STDOUT $message; } else { open (LOG, ">>$logfile"); print LOG $date.": ".$message; close (LOG); } } sub daemonize { my ($home); if (fork()) { # parent exit(0); } else { # child &write_log ("Guardian process id $$\n"); $home = (getpwuid($>))[7] || die "No home directory!\n"; chdir($home); # go to my homedir setpgrp(0,0); # become process leader close(STDOUT); close(STDIN); close(STDERR); print "Testing...\n"; } } sub sig_handler_setup { $SIG{INT} = \&clean_up_and_exit; # kill -2 $SIG{TERM} = \&clean_up_and_exit; # kill -9 $SIG{QUIT} = \&clean_up_and_exit; # kill -3 # $SIG{HUP} = \&flush_and_reload; # kill -1 } sub remove_blocks { my $source; my $time = time(); foreach $source (keys %hash) { if ($hash{$source} < $time) { &call_unblock ($source, "expiring block of $source\n"); delete ($hash{$source}); } } } sub call_unblock { my ($source, $message) = @_; &write_log ("$message"); system ("$unblockpath $source $interface"); } sub clean_up_and_exit { my $source; &write_log ("received kill sig.. shutting down\n"); foreach $source (keys %hash) { &call_unblock ($source, "removing $source for shutdown\n"); } exit; } sub load_targetfile { my $count = 0; open (TARG, "$targetfile") or die "Cannot open $targetfile\n"; while () { chop; next if (/\#/); #skip comments next if (/^\s*$/); # and blank lines $targethash{$_}=1; $count++; } close (TARG); print "Loaded $count addresses from $targetfile\n"; } sub get_aliases { my $ip; print "Scanning for aliases on $interface and add them to the target hash..."; open (IFCONFIG, "/sbin/ip addr show $interface |"); my @lines = ; close(IFCONFIG); foreach $line (@lines) { if ( $line =~ /inet (\d+\.\d+\.\d+\.\d+)/) { $ip = $1; print " got $ip on $interface ... "; $targethash{'$ip'} = "1"; } } print "done \n"; }