#!/usr/bin/perl # # title: Rat Trap # version: 1.1 # author: Nick Lindsell, Rentokil-Initial, nlindsell@rentokil.com # # function: Monitors Snort reports and adds offending IP addresses # to blocked list in firewall. # # features: Uses a FIFO to read from Snort without writing to disk. # Will set blocking rule on first Snort alert. # Logs via syslog to remote machine. # Logs blocked addresses to /var/sysconfig/blacklist. # Friendlies are in /var/sysconfig/whitelist - they will be ignored. # Releases blocked addresses after $TIMEOUT seconds. # # requires: Snort logging with local3 to syslog. # ( in snort.conf: "output alert_syslog: LOG_LOCAL3" ) # Syslog logging local3.notice to the fifo /var/log/snort.fifo # "mkfifo /var/log/snort.fifo" # ( in /etc/syslog.conf: "local3.* |/var/log/snort.fifo" ) # # incept date: Wed Aug 4 11:21:59 BST 2004 # # changelog: # Tue Mar 15 14:44:18 GMT 2005; fixed broken whitelist detection. # Wed May 18 06:42:35 BST 2005; fixed whitelist with tabs. # Fri Jul 15 09:23:05 BST 2005; fixed blocked ips not being removed from rules. # # # # # use IO::File; use Sys::Syslog; $CONFIG="/etc/sysconfig/ratrap.conf"; select STDOUT; $|=1; select STDERR; $|=1; $MULTICAST="224"; my %blocked; # hash of blocked addresses and the timestamp of the last attack. my %friends; # hash of friendly addresses. &getconfig; writelog("### # $0 started on $MY_IP_ADDR \($INTERFACE\)\n"); if( -e $WHITELIST ){ unless(open(WHITELIST,"$WHITELIST")){ writelog("Cannot open whitelist $WHITELIST: $!"); print("Cannot open whitelist $WHITELIST: $!\n"); } while(){ unless(substr($_,0,1) eq "#"){ chomp; s/\s+//; s/\t+//; s/\s+$//; ($ip,$comment)=split(/\#/,$_); $ip=~s/ //g; if(index($ip,"-") > 0){ # is a network range ($n1,$n2)=split(/\-/,$ip); $netstart=&padip($n1); $netend=&padip($n2); $ip=$netstart."-".$netend; } else { # just an address $ip=&padip($ip); } $friends{$ip}=$comment; # add to friends hash. writelog("$ip added to friends from whitelist."); } } # writelog("friends = @{[ %friends ]}"); } if( -e $BLACKLIST ){ open(BLACKLIST,"$BLACKLIST") or die "Cannot open blacklist $BLACKLIST: $! \n"; while(){ chomp; unless(substr($_,0,1) eq "#"){ ($atime,$ip)=split(/\:/,$_); writelog("Adding $ip to firewall from pre-existing $BLACKLIST"); &block_ip($ip); # firewall any old culprits. $blocked{$ip}=$atime; # add to blocked hash. } } close BLACKLIST; } if($snortpid=fork){ # fork a child process to watch the FIFO. writelog("ratrap:snortwatcher thread starting, pid=$snortpid"); unless(open(FIFO,"<$SNORT_FIFO")){ print "Cannot open fifo $SNORT_FIFO: $!"; writelog("Cannot open fifo $SNORT_FIFO: $!"); exit(); } while (){ # parse the snort alerts. unless(open(BLACKLIST,"> $BLACKLIST")){ writelog("Cannot open blacklist $BLACKLIST: $!"); print("Cannot open blacklist $BLACKLIST: $!\n"); } chomp; @log=split(/:/,$_,4); $info="$log[0] $log[1] $log[2]"; $hostname=(split(/\s+/,$info))[5]; $agent=(split(/\s+/,$info))[6]; $message=$log[3]; if($agent eq "snort"){ # we are looking at a Snort alert. @message=split(/\[|\]/,$message); $attack_type=$message[2]; # attack type string. $attack_class=(split(/:/,$message[3]))[1]; # attack class string. $attack_priority=(split(/:/,$message[5]))[1]; # attack priority level (1-3). $ip_info=$message[6]; # $ip_protocol=(split(/\{|\}/,$ip_info))[1]; # attack protocol (UDP,TCP,ICMP etc). $ip_src=(split(/\s+/,$ip_info))[2]; ($ip_src_addr,$ip_src_port)=split(/:/,$ip_src); # attackers ip address and port. $ip_dest=(split(/\s+/,$ip_info))[4]; ($ip_dest_addr,$ip_dest_port)=split(/:/,$ip_dest); # destination ip address and port. $ip_src_addr_octet3=(split(/\./,$ip_src_addr))[0]; # highest octet of attackers address. $friendly=0; while(($netinfo,$comment) = each %friends){ $netinfo=~s/ //g; $badip=&padip($ip_src_addr); if(index($netinfo,"-") > 0){ # $netinfo is a network range ($netstart,$netend)=split(/\-/,$netinfo); if (($badip >= $netstart) && ($badip <= $netend)){ $friendly++; # writelog("Ignoring $ip_src_addr (in whitelisted address range) $netstart < $badip > $netend"); } } else { # $netinfo is a network address if ($badip eq $netinfo){ $friendly++; # writelog("Ignoring $ip_src_addr (whitelisted address) $badip $netinfo"); } } } if ( ($attack_priority <= 2) # if priority <= 2; then danger. &&($ip_src_addr) # and we have a src sddress, &&($ip_src_addr ne $MY_IP_ADDR) # but its not ours, &&($ip_src_addr ne $MY_GW_ADDR) # nor is it the gateway, &&(! $friendly) # nor is it a friendly, &&($ip_src_addr_octet3 <= $MULTICAST) ){ # nor is it a multicast. $atime=time(); # then get timestamp in seconds since the epoch. if (! exists($blocked{$ip_src_addr}) ){ # if not in block hash, $blocked{$ip_src_addr}=$atime; # add to block hash. writelog("Adding $ip_src_addr to blacklist. Attack method \"$attack_type\" detected by Snort."); &block_ip($ip_src_addr); # add to firewall, } else { # writelog("Updating timestamp for $ip_src_addr to $atime"); $blocked{$ip_src_addr}=$atime; # else just update timestamp to block for another $TIMEOUT seconds. } } while(($ip,$t) = each %blocked){ print(BLACKLIST "$t:$ip\n"); # write back to blacklist to preserve state. } close BLACKLIST; } if($agent eq "timer"){ # This is a timer alert to check for timed-out blocked IPs. $now=time(); while(($ip,$t) = each %blocked){ if( ($t+$TIMEOUT) > $now ){ print(BLACKLIST "$t:$ip\n"); } else { writelog("Timeout on $ip, $now-$t >$TIMEOUT"); delete $blocked{$ip}; # drop from blocked hash. &unblock_ip($ip); # drop from firewall. } } close BLACKLIST; } } exit(0); } select STDOUT; $|=1; select STDERR; $|=1; if($timerpid=fork){ # timer thread. writes alarm to FIFO every $ALARMTICK seconds. writelog("ratrap:timer thread starting, pid=$timerpid"); # the snortwatch thread reads this and checks the timeouts on blocked IPs. while(1) { unless(open(FIFO,">$SNORT_FIFO")){ print "Cannot write fifo $SNORT_FIFO: $!"; writelog("Cannot write fifo $SNORT_FIFO: $!"); } my $t=time(); ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); my $month=(Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[$mon]; if($mday < 10){$mday=" ".$mday}; if($hour < 10){$hour="0".$hour}; if($min < 10){$min="0".$min}; if($sec < 10){$sec="0".$sec}; my $date = "$month $mday $hour:$min:$sec"; print FIFO "$date localhost timer: $t\n"; # send alarm call to snortwatch thread through the fifo. sleep($ALARMTICK); } exit(0); } sub writelog { my $message = $_[0]; my $ltime=localtime(); open(LOG,">>$LOG") or die "Cannot open log $LOG: $! \n"; select LOG; $|=1; print LOG "$ltime: $message \n"; close LOG; openlog("ratrap","ndelay","daemon"); syslog("$SYSLOG_LEVEL",$message); closelog(); } sub block_ip { my $ip = $_[0]; $exists = &checkfw($ip); if (! $exists){ foreach $chain (@CHAINS){ my $rule="$IPTABLES -I $chain -s $ip -i $INTERFACE -j DROP"; writelog("Adding $ip to firewall rules - $IPTABLES -I $chain -s $ip -i $INTERFACE -j DROP"); system("$rule"); } return(1); } else { writelog(" $ip already in firewall rules - skipped"); return(0); } } sub unblock_ip($ip){ my $ip = $_[0]; $exists = &checkfw($ip); if ($exists){ foreach $chain (@CHAINS){ my $rule="$IPTABLES -D $chain -s $ip -i $INTERFACE -j DROP"; writelog("Dropping $ip from $chain firewall chain."); system("$rule"); } return(1); } else { writelog("$ip not in firewall rules - skipped"); return(0); } } sub checkfw($ip){ my $ip=$_[0]; unless(open(IPTABLES,"$IPTABLES -L -n |")){ print "Cannot open $IPTABLES : $!"; writelog("Cannot open $IPTABLES : $!"); } else { $i=1; while(){ @rule=split(/\s+/,$_); if(($rule[0] eq "DROP") && ($rule[3] eq $ip)){ return(1); } $i++; } return(0); } } sub getconfig(){ open (CONF,"$CONFIG") or die "Cannot read the config file $CONFIG, $!\n"; while () { chop; next if (/^\s*$/); # skip blank lines next if (/^#/); # skip comment lines my ($var,$value,$comment)=split(/\s+/,$_); if ($var eq "LogFile") { $LOG = $value; } if ($var eq "Blacklist") { $BLACKLIST = $value; } if ($var eq "Whitelist") { $WHITELIST = $value; } if ($var eq "SnortFIFO") { $SNORT_FIFO = $value; } if ($var eq "Gateway") { $MY_GW_ADDR = $value; } if ($var eq "SyslogHost") { $SYSLOG_HOST = $value; } if ($var eq "SyslogLevel") { $SYSLOG_LEVEL = $value; } if ($var eq "TriggerLevel") { $PRIORITY_TRIGGER = $value; } if ($var eq "IPtables") { $IPTABLES = $value; } if ($var eq "IptableChains") { @CHAINS = split(/\,/,$value); } if ($var eq "IProute") { $ROUTE = $value; } if ($var eq "IFconfig") { $IFCONFIG = $value; } if ($var eq "PublicInterface") { $INTERFACE = $value; } if ($var eq "Timeout") { $TIMEOUT = $value; } if ($var eq "AlarmPeriod") { $ALARMTICK = $value; } } close CONF; open(IFCONFIG,"$IFCONFIG $INTERFACE |") or die "Cannot open pipe $IFCONFIG $if: $1" ; while(){ @line=split(/\s+/,$_); if($line[1] eq "inet"){ $MY_IP_ADDR=(split(/:/,$line[2]))[1]; } } open(ROUTE," $ROUTE |") or die "Cannot open pipe $ROUTE: $1" ; while(){ chomp; @route=split(/\s+/,$_); if( ($route[3] eq "UG") && ($route[7] eq $INTERFACE) ){ $MY_GW_ADDR=$route[1]; } } unless("$LOG"){ $LOG="/var/log/ratrap.log"; } unless("$BLACKLIST"){ $BLACKLIST="/var/log/blacklist"; } unless("$WHITELIST"){ $WHITELIST="/var/log/whitelist"; } unless("$SNORT_FIFO"){ $SNORT_FIFO="/var/log/snort.fifo"; } unless("$MY_IP_ADDR"){ print "$0: IP address not set!"; exit; } unless("$SYSLOG_LEVEL"){ $SYSLOG_LEVEL="local4.notice"; } unless("$PRIORITY_TRIGGER"){ $PRIORITY_TRIGGER=2; } unless("$IPTABLES"){ $IPTABLES="/usr/sbin/iptables"; } unless("$CHAINS[0]"){ @CHAINS="INPUT"; } unless("$IFCONFIG"){ $IFCONFIG="/sbin/ifconfig"; } unless("$INTERFACE"){ print "$0: Interface not set"; exit; } unless("$TIMEOUT"){ $TIMEOUT="3600"; } unless("$ALARMTICK"){ $ALARMTICK="60"; } writelog( "## $0 settings:-"); writelog( "-- LogFile= $LOG "); writelog( "-- Blacklist= $BLACKLIST"); writelog( "-- Whitelist= $WHITELIST"); writelog( "-- SnortFIFO= $SNORT_FIFO"); writelog( "-- IPaddress= $MY_IP_ADDR"); writelog( "-- Gateway= $MY_GW_ADDR"); writelog( "-- SyslogHost= $SYSLOG_HOST"); writelog( "-- SyslogLevel= $SYSLOG_LEVEL"); writelog( "-- TriggerLevel= $PRIORITY_TRIGGER"); writelog( "-- IPtables= $IPTABLES "); writelog( "-- IPtableChains= @CHAINS "); writelog( "-- IFconfig= $IFCONFIG "); writelog( "-- IProute= $ROUTE "); writelog( "-- PublicInterface= $INTERFACE"); writelog( "-- Timeout=$TIMEOUT "); writelog( "-- AlarmPeriod= $ALARMTICK"); writelog( "## $0 -:settings end"); } sub padip() { my $ip=$_[0]; my $paddedip; @octets=split(/\./,$ip); if($octets[0] <= 9){ $octets[0]="0".$octets[0];} if($octets[0] <= 99){ $octets[0]="0".$octets[0];} if($octets[1] <= 9){ $octets[1]="0".$octets[1];} if($octets[1] <= 99){ $octets[1]="0".$octets[1];} if($octets[2] <= 9){ $octets[2]="0".$octets[2];} if($octets[2] <= 99){ $octets[2]="0".$octets[2];} if($octets[3] <= 9){ $octets[3]="0".$octets[3];} if($octets[3] <= 99){ $octets[3]="0".$octets[3];} $paddedip=$octets[0].$octets[1].$octets[2].$octets[3]; return($paddedip); } close(STDOUT); close(STDIN); close(STDERR); exit(0);