#!/usr/bin/perl -w # # dav-black.pl Version 2.0 # # based on mailmgr (c) 2003, Julian Haight, All Rights reserved under GPL license: # http://www.gnu.org/licenses/gpl.txt # Modified on 02AUG04 as provided under GNU GPL licensing # no rights reserved under this modification # # See http://www.pettingers.org/code/davblack.html for details on this # # This is a script which tails web log files and dynamically blocks # connections from hosts which meet certain criteria, using # command-line kernel-level firewall configuration tools provided by # underlying operating system (iptables) # As the script is modifying iptables, it will need root access to do so. # # Note: this script can also be modified to monitor ANY log file # including access (secure) logs and sendmail (mail) logs. The # aggressiveness can be adjusted by setting the variables in the # first few lines. It will probably work well right out of the box. # Modifications will also be required for use of ipchains instead of # iptables. # # Setup: You need to create the initial chain that dav-black will work with. # For iptables, you would do this: # iptables -N WEB ## Create a new chain called WEB # Then you would do this: # iptables -A INPUT -p tcp -m tcp --dport 80 --syn -j WEB ## Send all TCP port 80 packets through the chain. We will be adding ## REJECT jumps to this chain with the program below. # # The easiest way to run the script is in the background with a shell script, # you can then put the shell script into /etc/rc.d/rc.local to run at start-up. # Something like: # #! /bin/bash # /root/utils/dav-black.pl >>/var/log/web-blacklisting 2>&1 & # # This will create a nice log of your activities at /var/log/web-blacklisting ############################################################################## use strict; use Socket; # The log file you want to monitor my($LOG) = '/var/log/httpd/access_log'; # The cache file to keep track of attackers my($CACHE) = '/var/tmp/web-blacklist-pending'; # regex for whitelisted IPs - never blacklist these addresses my($LOCALNET) = '^(?:127\.0\.0\.1|192\.168\.0)'; # your kernel-firewall, see "man iptables" to redefine params used my($IPTABLES) = '/sbin/iptables'; my($ADDRULE) = '-I'; # cmdline for insert rule my($DELRULE) = '-D'; # cmdline for delete rule # Regex of reasons to get firewalled. Separate with pipe (|). # This VARIES BASED ON THE VERSION OF SOFTWARE YOU ARE RUNNING # Look at your logs and adjust as necessary. # my($REASONS) = '(OPTIONS|PROPFIND|TRACE|CONNECT|SEARCH|fp30reg|tickerbar|cgi-bin/awstats|scripts|vti_bin|mem_bin|winnt|system32|msadc|global.asa|iissamples|sumthin|default.ida|prxjdg|userstat.pl|NULL.printer|formmail)'; # # Maximum time (sec) before they are removed from the database # unless they are already blacklisted my($AGEOUT) = 86400; # Time delay (day) before they are released from the blacklist in DAYS! my($RELEASEDAYS) = 3; # Time dealy (sec) to check the database for cleanup my($CHECK) = 600; # Maximum number of booboos before they get listed my($MAXHITS) = 2; # # # ########### No user defined paramters below ################ # my($OCT) = '(?:25[012345]|2[0-4]\d|1?\d\d?)'; my($IP) = $OCT . '\.' . $OCT . '\.' . $OCT . '\.' . $OCT; $RELEASEDAYS *= 86400; # Lots of seconds! # $RELEASEDAYS = 130; #For testing print "\nInitializing...\n"; # Poor man's touch command open (TOUCH, ">> $CACHE"); close (TOUCH); # Start the monitoring taillog(); sub taillog { my($offset, $name, $line, $ip, $reason, $stall, $ind) = ''; my (@loser, @buildlist) = (); $offset = (-s $LOG); # Don't start at begining, go to end while (1==1) { sleep(1); $| = 1; $stall += 1; if ((-s $LOG) < $offset) { print "Log shrunk, resetting..\n"; $offset = 0; } open(TAIL, $LOG) || print STDERR "Error opening $LOG: $!\n"; if (seek(TAIL, $offset, 0)) { # found offset, log not rotated } else { # log reset, follow $offset=0; seek(TAIL, $offset, 0); } while ($line = ) { chop($line); if (($REASONS) && ($line =~ m/$REASONS/)) { $reason = $1; if ($line =~ m/($IP)/) { $ip = $1; open(LIST, $CACHE) || print STDERR "Error opening $CACHE: $!\n"; $ind = 0; @buildlist = ; foreach $line(@buildlist) { @loser = split(/,/, $line); # [0] is IP, [1] is time, [2] is hits if ($loser[0] eq $ip) { # Already listed, increase count $loser[2] += 1; if ($loser[2] == $MAXHITS) { # See ya! print "$ip being blocked because of $reason, \n"; blockIp($ip); print " Killed ", scalar localtime, "\n"; print "-------------------------------------------\n"; $loser[2] += 1; # Avoid double listings (???) } $line = join(',', @loser); # put back together for saving $line .= "\n"; $buildlist[$ind] = $line; $ip = 'logged'; } # End if already listed $ind += 1; } # End foreach read close (LIST); if ($ip ne 'logged') { $line = $ip . ',' . time() . ',' . 1 . "\n"; # $line = join(',', $ip time() '1' "\n"); push (@buildlist, $line); } open (LIST, ">$CACHE") || print STDERR "Error opening $CACHE: $!\n"; print LIST @buildlist; close (LIST) } # End if IP next; } # End if match reasons } # End while read line $offset=tell(TAIL); close(TAIL); if ($stall >= $CHECK) { # Time to do cleanup $stall = 0; @buildlist = (); open(LIST, $CACHE) || print STDERR "Error opening $CACHE: $!\n"; while ($line = ) { @loser = split(/,/, $line); # [0] is IP, [1] is time, [2] is hits if ($loser[2] >= $MAXHITS) { # already blacklisted if (($loser[1] + $RELEASEDAYS) > time()) { push (@buildlist, $line); } else { iptables($DELRULE, $loser[0]); print "Freeing $loser[0]", " on ", scalar localtime, "\n"; print "-------------------------------------------\n"; } #set free after $RELEASEDAYS } elsif (($loser[1] + $AGEOUT) > time()){ # Not listed and not aged out push (@buildlist, $line); } } # End while reading close (LIST); # open for writing open (LIST, ">$CACHE") || print STDERR "Error opening $CACHE: $!\n"; print LIST @buildlist; close (LIST); @buildlist = (); } # End cleanup check } # End while endless loop } # End sub taillog sub blockIp { my($ip) = @_; if ($ip =~ m/$LOCALNET/) { print "WHITELISTED HOST - NOT BLOCKING \n"; return; } iptables($ADDRULE, $ip); return; } # End sub blockIp sub iptables { my($action, $ip) = @_; my(@args) = ($IPTABLES, $action, 'WEB', '--source', $ip, '-j', 'REJECT', '--reject-with', 'icmp-host-prohibited'); system(@args); return; } # End sub iptables