I believe this script has been SIGNIFICANTLY deprecated by the ModSecurity addition to Apache. I leave it up here for the historical value and for those people who are not using Apache. If you still need something like this, please look at sshblack as it is very similar and much more up to date than this code.
This is Version 2.0 of the popular dav-black script. It provides variable aggressiveness by allowing the administrator to adjust the number of 'hits' required to make the blacklisting fire. Additionally, it will remove addresses from the blacklist after a user-defined time period. The problem of duplicate table listings in the old version is also fixed...I think.
DAVBlack is based on the work of Julian Haight. His mailmgr program was a bit more than I needed. Yet I liked the idea of tailing a log file.
After I noticed a large quantity of "Microsoft-WebDAV-MiniRedir/" entries in my Apache logs I started Googling around to find out what these were. All indications seem to point to these being harmless and possibly unintentional "attacks." However, I was tired of seeing them in my logs. I would get hundreds of these entries a day -- particularly on my DSL line -- and it made my log difficult to read.
Ideally these could have been handled with blockme.cgi except all the entries were using HTTP methods of OPTIONS and PROPFIND instead of GET. Additionally, there are indications that certain blacklists like SORBS may list you as an "exploitable" web server if you respond to certain attacks with an HTTP response code of 200 (which blockme.cgi does). Certainly I could have had Apache block these methods but this would not have stopped the "attacking" sites from continually showing up in my logs. They needed to be blacklisted.
#!/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|CONNECT|SEARCH|fp30reg|tickerbar|scripts|vti_bin|mem_bin|\ |system32|msadc|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 = <TAIL>) { 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 = <LIST>; 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 = <LIST>) { @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
Be sure to visit the Geek Page for more geeky stuff.
pettingers.org