#!c:/perl/bin/perl.exe -w # $Revision: 1.1 $ # $Source: /usr/local/cvsroot/boxes/scripts/brave/ban-hackers.pl,v $ # %Location: /usr/local/sbin/ # %Servers: brave # Every minute check the database for new ip# to ban #* * * * * c:\perl\bin\perl.exe c:/apache228/bin/ban-hackers.pl add # # Every hour purge expired entries #0 * * * * c:\perl\bin\perl.exe c:/apache228/bin/ban-hackers.pl purge # # to see the entries use /banhackers.pl list # SecRule RESPONSE_STATUS "^418$" "exec:/usr/local/apache2/bin/ban-hackers.pl,msg:'Address has been blocked',severity:5,phase:5" # script to take ip# hit by rules in apache mod_security # and feed them to iptables to ban access to port 80 (http) # BAN-HACKERS.PL written by Stanislaw Polak # http://www.icsr.agh.edu.pl/~polak/ # 30 Nov 2006 # # Copyright (c) 2006 Stanislaw Polak # Unpublished work. # Permission granted to use and modify this script so long as the # copyright above is maintained, modifications are documented, and # credit is given for any use of the script. # # For more information, see: # http://www.icsr.agh.edu.pl/~polak/skrypty/ban-hackers.var # #Version 1.03 (7 Apr 2008) # Some modifications made by Dan MacNeil #Version 1.02 (24 Jan 2007) # Locking was added #Version 1.01 (27 Dec 2006) # Problems with deleting a few the same rules has been fixed #Version 1.0 (30 Nov 2006) # The first public version ###################################################################### ################################ # Requirements: grep installed and DBD::SQLite for perl # You Need to create the log file and you have to have "ipfw for windows" installed, of course ###################################################################### ################################ use DBI; use strict; use Fcntl qw (:flock); my($ip,$rule,$dbh,$standard_period,$nonstandard_period,$max_elements,% whitelist,$dbname); ###################################################################### ################################ # $standard_period | elements which were added $standard_period seconds ago or ealier can be purged ###################################################################### ################################ # $max_elements | when number of elements in the database exceedes $max_elements, elements # $nonstandard_period | which were added $nonstandard_period seconds ago or ealier can be purged ###################################################################### ################################ # %whitelist | hosts listed in %whitelist are never added to the database ###################################################################### ################################ # $dbname | data are stored in the "$dbname" database ###################################################################### ################################ $standard_period=5*24*60*60; #120 hours --- not tested yet but might work???? $nonstandard_period=24*60*60; #24 hours $max_elements=2000; %whitelist=( "192.168.0.3"=>0, "192.168.0.10"=>0, "192.168.0.203"=>0 ); ###################################################################### ################################ $dbname="C:/Apache228/logs/hackers.sqlite"; ###################################################################### ################################ ############## open lock file ############## ###################################################################### ################################ my $lock_file = 'C:/Apache228/tmp/ban-hackers.lock'; ###################################################################### ################################ open LOCKFILE, "<$lock_file" or open LOCKFILE, ">>$lock_file" or warn "Cannot open $lock_file"; #$ip=$ENV{HTTP_X_FORWARDED_FOR}; $ip=$ENV{REMOTE_ADDR}; $dbh=&open_db; #is script executed without arguments if($#ARGV==-1 && $ip) { # add actual IP to the database # the script you execute must write something (anything) to stdout. # If it doesn't ModSecurity will assume execution didn't work # If you write something to stdout cron will email it to you # so don't do that if running from cron and not Mod_Security print "\t.\n"; ###################################################################### ################################ open (LOGFILE, '>> C:/Apache228/logs/ip.log') or die "Can't open ip.log: $!"; select LOGFILE; print "Ran: ", scalar(localtime), "\t$ip\n"; close LOGFILE; ###################################################################### ################################ # if connection was via proxy, address contains a list of client's IP # and proxies' IP - hacker's IP will be IP of last proxy my @ips=split(',',$ip); #the above addresses (elements of the list) are separated by "," $ip=$ips[$#ips]; #hacker's IP is the last IP from the list $ip =~s/^ *//; #Remove leading spaces: # Addition for Rule Numbering when using "ipfw" rather than "iptables" my ($sec,$rmin,$rhr,$mday,$mon,$year,$rday,$yday,$isdst) = localtime(time); # $rday - day of the week [0-6] (Sun. is '0') my $starter = 30000 + ($rday * 1440); # minutes in a day? 24 * 60 = 1440 my $rule = ((($rhr * 60) + $rmin) + $starter); # 30000 == "start_rule" and 40079 == "end_rule" # there can be more than one rule with the same number in "ipfw" so its OK flock(LOCKFILE, LOCK_EX); if( !exists $whitelist{$ip} && !hacker_exists($dbh,$ip)){ &add_hacker_db($dbh,$ip,time,$rule); #add hacker's IP if it doesn't exists yet #check if actual IP exists on the whitelist or in the database # &add_hacker_db($dbh,$ip,time); #add hacker's IP if it doesn't exists yet } flock(LOCKFILE, LOCK_UN); } #when script is executed with the "purge" argument elsif($#ARGV==0 && $ARGV[0] eq "purge"){ #purge the content of the database flock(LOCKFILE, LOCK_EX); &purge($dbh); flock(LOCKFILE, LOCK_UN); } #when script is executed with the "add" argument elsif($#ARGV==0 && $ARGV[0] eq "add"){ #add the content of a database to iptables flock(LOCKFILE, LOCK_EX); &add_hacker_iptables($dbh); flock(LOCKFILE, LOCK_UN); } #when script is executed with the "list" argument elsif($#ARGV==0 && $ARGV[0] eq "list"){ #list the content of the database flock(LOCKFILE, LOCK_SH); my $query = qq{select * from hackers}; my $sth= $dbh->prepare($query); $sth->execute; print "Hacker's IP\tWhen added\t\t\tRule\n",'-' x 53,"\n"; while (my (@data) = $sth->fetchrow_array) { my $time = localtime($data[1]); #print "$data[0]\t$time\n"; # Edit for "ipfw" use print "$data[0]\t$time\t$data[2]\n"; } flock(LOCKFILE, LOCK_UN); } $dbh->disconnect; close LOCKFILE or die "Cannot close $lock_file"; ###################################################################### ######### # opens a database and creates the "hackers" table if it wasn't created ealier sub open_db{ my $dbh = DBI->connect("dbi:SQLite:dbname=$dbname","","") or die "Database connection not made: $DBI::errstr"; if (! table_exists( $dbh, "hackers")) { #if the "hackers" table doesn't exists # edit for "ipfw" my $create_query = qq{create table hackers ( ip text unique, time integer not null, rule text not null ) }; $dbh->do( $create_query ); #create it } return $dbh; #return a handler to the database } ##################################################### # add the IP and time stamp to the "hackers" database sub add_hacker_db{ my $dbh = shift; my $ip = shift; my $time = shift; my $rule = shift; my @fields = (qw(ip time rule)); my $fieldlist = join ", ", @fields; my $field_placeholders = join ", ", map {'?'} @fields; my $insert_query = qq{ INSERT INTO hackers ( $fieldlist ) VALUES ( $field_placeholders )}; my $sth= $dbh->prepare( $insert_query ); $sth->execute($ip,$time,$rule); } ################################################# # add IPs stored in the database to the iptables sub add_hacker_iptables{ my $query = qq{select ip, rule from hackers}; my $sth= $dbh->prepare($query); $sth->execute; # addition for "ipfw" ###################################################################### ################################ my @current_rules=`C:/WINNT/system32/ipfw.exe -d list`; ###################################################################### ################################ while (my ($ip,$rule) = $sth->fetchrow_array) { # don't create duplicate iptable rule next if scalar grep {/$ip/} @current_rules; # need grep ?????? # addition for "ipfw" ###################################################################### ################################ my $cmd = "C:/WINNT/system32/ipfw.exe -q add $rule drop log ip from $ip to any"; ###################################################################### ################################ (system($cmd)==0) or warn "returned failure: $cmd"; } } ###################################################################### ######### #check if the "ip" of hacker exists in database sub hacker_exists{ my $dbh = shift; my $ip = shift; my $query = qq{select * from hackers where ip='$ip'}; my $sth= $dbh->prepare($query); $sth->execute; my($result) = $sth->fetchrow_array; if($result){ return length($result); } else { return 0; } } ###################################################################### ######### # remove hachers IP after selected time sub purge{ my $dbh= shift; my($time)=time; # #it contains all the rules from 'iptables' if($max_elements == 0 && $standard_period == 0){return;} #if there is less than $max_elements in the database if(count_hackers($dbh) <= $max_elements && $standard_period > 0){ $time -= $standard_period; #remove elements which are older than standard period } elsif($max_elements > 0) { $time -= $nonstandard_period; #else remove elements which are older than non-standard period } my $query = qq{select ip,rule from hackers where time <= $time}; my $sth= $dbh->prepare($query); $sth->execute; my @ips =(); my @rules =(); while (my ($ip,$rule) = $sth->fetchrow_array) { push @ips, $ip; push @rules, $rule; } foreach $rule (@rules){ #remove "old" element from the database and from iptables ###################################################################### ################################ my $cmd="C:/WINNT/system32/ipfw.exe -q delete $rule"; ###################################################################### ################################ (system($cmd)==0) or warn "returned failure: $cmd"; } foreach $ip (@ips){ #remove "old" element from the database $dbh->do(qq{ delete from hackers where ip='$ip' } ) or die $dbh->errstr; } } ###################################################################### ######### #check if the table exists in the data base # source: http://gmax.oltrelinux.com/dbirecipes.html sub table_exists { my $db = shift; my $table = shift; my @tables = $db->tables('','','','TABLE'); if (@tables) { for (@tables) { next unless $_; return 1 if $_ eq $table } } else { eval { local $db->{PrintError} = 0; local $db->{RaiseError} = 1; $db->do(qq{SELECT * FROM $table WHERE 1 = 0 }); }; return 1 unless $@; } return 0; } ###################################################################### ######### # count the number of elements in the hackers table sub count_hackers{ my $dbh= shift; my $query=qq{SELECT COUNT(*) FROM hackers}; my $sth= $dbh->prepare($query); $sth->execute; my ($count) = $sth->fetchrow_array; return $count; }