diff --git a/aries/firewall/webabuse.pl b/aries/firewall/webabuse.pl new file mode 100644 index 0000000..561289c --- /dev/null +++ b/aries/firewall/webabuse.pl @@ -0,0 +1,94 @@ +#!/usr/local/bin/perl -T +use strict; use warnings; +## Calomel.org .:. https://calomel.org name : web_server_abuse_detection.pl version : 0.04 +# description: this script will watch the web server logs (like Apache or Nginx) and count the number of http +# error codes an ip has triggered. At a user defined amount of errors we can execute a action to block the ip +# using our firewall software. +## which log file do you want to watch? + my $log = "/var/log/nginx/access.log"; + #my $log = "/var/log/nginx/error.log"; +## how many errors can an ip address trigger before we block them? + my $errors_block = 10; +## how many seconds before an unseen ip is considered old and removed from the hash? + my $expire_time = 7200; +## how many error log lines before we trigger blocking abusive ips and clean up of old ips in the hash? make +## sure this value is greater than $errors_block above. + my $cleanup_time = 10; +## do you want to debug the scripts output ? on=1 and off=0 + my $debug_mode = 1; +## clear the environment and set our path + $ENV{ENV} =""; $ENV{PATH} = "/bin:/usr/bin:/usr/local/bin"; +## declare some internal variables and the hash of abusive ip addresses + my ( $ip, $errors, $time, $newtime, $newerrors ); my $trigger_count=1; my %abusive_ips = (); +## open the log file. we are using the system binary tail which is smart enough to follow rotating logs. We +## could have used File::Tail, but tail is easier. + open(LOG,"/usr/bin/tail -F $log |") || die "ERROR: could not open log file.\n"; while() { + ## process the log line if it contains one of these error codes + if ($_ =~ m/( 301 | 302 | 303 | 307 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 408 | 409 | 410 | 411 | + 412 | 413 | 414 | 415 | 416 | 444 | 494 | 495 | 496 | 497 | 500 | 501 | 502 | 503 | 504 | 507 )/) + { + ## Whitelisted ips. This is where you can whitelist ips that cause errors, but you do NOT want them to + ## be blocked. Googlebot at 66.249/16 is a good example. We also whitelisted the private subnet + ## 192.168/16 so web developers inside the firewall can test and never be blocked. + if ($_ !~ m/^(66\.249\.|192\.168\.)/) { + ## extract the ip address from the log line and get the current unix time + $time = time(); $ip = (split ' ')[0]; + ## if an ip address has never been seen before we need to initialize the errors value to avoid warning + ## messages. + $abusive_ips{ $ip }{ 'errors' } = 0 if not defined $abusive_ips{ $ip }{ 'errors' }; + ## increment the error counter and update the time stamp. + $abusive_ips{ $ip }{ 'errors' } = $abusive_ips{ $ip }->{ 'errors' } + 1; $abusive_ips{ $ip }{ 'time' } + = $time; + ## DEBUG: show detailed output + if ( $debug_mode == 1 ) { $newerrors = $abusive_ips{ $ip }->{ 'errors' }; $newtime = $abusive_ips{ $ip + }->{ 'time' }; + print "unix_time: $newtime, errors: $newerrors, ip: $ip, cleanup_time: $trigger_count\n"; + } + ## if an ip has triggered the $errors_block value we block them + if ($abusive_ips{ $ip }->{ 'errors' } >= $errors_block ) { + ## DEBUG: show detailed output + if ( $debug_mode == 1 ) { print "ABUSIVE IP! unix_time: $newtime, errors: $newerrors, ip: $ip, + cleanup_time: $trigger_count\n"; + } + ## Untaint the ip variable for use by the following external system() calls + my $ip_ext = "$1" if ($ip =~ m/^([0-9\.]+)$/ or die "\nError: Illegal characters in ip\n\n" ); + ## USER EDIT: this is the system call you will set to block the abuser. You can add the command + ## line you want to execute on the ip address of the abuser. For example, we are using logger to + ## echo the line out to /var/log/messages and then we are adding the offending ip address to our + ## FreeBSD Pf table which we have setup to block ips at Pf firewall. + system("/usr/bin/logger", "$ip_ext", "is", "abusive,", "sent", "to", "BLOCKTEMP"); + system("/sbin/pfctl", "-t", "BLOCKTEMP", "-T", "add", "$ip_ext"); + ## send an email with the blocked IP + system("/bin/sh", "/usr/local/etc/email_abusive_ip_logs.sh", "$ip_ext"); + ## after the ip is blocked it does need to be in the hash anymore + delete($abusive_ips{ $ip }); + } + ## increment the trigger counter which is used for the following clean up function. + $trigger_count++; + ## clean up function: when the trigger counter reaches the $cleanup_time we remove any old hash entries + ## from the $abusive_ips hash + if ($trigger_count >= $cleanup_time) { my $time_current = time(); + ## DEBUG: show detailed output + if ( $debug_mode == 1 ) { print " Clean up... expire: $expire_time, pre-size of hash: " . keys( + %abusive_ips ) . ".\n"; + } + ## clean up ip addresses we have not seen in a long time + while (($ip, $time) = each(%abusive_ips)){ + ## DEBUG: show detailed output + if ( $debug_mode == 1 ) { my $total_time = $time_current - $abusive_ips{ $ip }->{ 'time' }; print + " ip: $ip, seconds_last_seen: $total_time, errors: $abusive_ips{ $ip }->{ 'errors' }\n"; + } + if ( ($time_current - $abusive_ips{ $ip }->{ 'time' } ) >= $expire_time) { + delete($abusive_ips{ $ip }); + } + } + ## DEBUG: show detailed output + if ( $debug_mode == 1 ) { print " Clean up... expire: $expire_time, post-size of hash: " . keys( + %abusive_ips ) . ".\n"; + } + ## reset the trigger counter + $trigger_count = 1; + } + } + } + } \ No newline at end of file