#!/usr/bin/perl # # title: Ratrap # version: 1.4 # author: root/at/blueskylinux/dot/net # # function: Monitors Snort reports and adds offending IP addresses # to tarpit 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. # Mon Sep 12 09:49:36 BST 2005; added TARPIT to blocking functions. # Thu Oct 27 14:17:19 GMT 2005; modified agent string detection for Snort v2.4.3. line 181. # Wed Nov 2 15:43:28 GMT 2005; added bridged test, line 151. # # # # # 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"); }elsif (defined $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(substr($agent,0,5) 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_dest_addr eq $MY_IP_ADDR)||(substr($INTERFACE,0,2) eq "br") ) # the attack is directed here or we are bridging, &&($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; } } } 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. } elsif (defined $timerpid) { 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); $rulenum=1; if (! $exists){ if ($tarpit){ my $rule="$IPTABLES -I INPUT $rulenum -p tcp -s $ip -j TARPIT"; writelog("Adding $ip to firewall rules - $rule"); system("$rule"); $rulenum++; } if ($drop){ foreach $chain (@CHAINS){ my $rule="$IPTABLES -I $chain $rulenum -i $INTERFACE -s $ip -j DROP"; writelog("Adding $ip to firewall rules - $rule"); 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){ &writelog("Dropping $ip from @CHAINS."); foreach $chain (@CHAINS){ @rulenumbers=`$IPTABLES -L $chain -n --line-numbers| grep $ip| cut -d \" \" -f1`; while($rulenum=pop(@rulenumbers)){ system("$IPTABLES -D $chain $rulenum"); &writelog("Dropping $ip from $chain firewall chain."); } } return(1); } else { &writelog("$ip not in firewall rules - skipped"); return(0); } } sub checkfw($ip){ my $ip=$_[0]; unless(open(IPTABLES,"$IPTABLES -L -n |")){ &writelog("Cannot open $IPTABLES : $!"); } else { $i=1; while(){ @rule=split(/\s+/,$_); if(($rule[0] eq "DROP") && ($rule[3] eq $ip)){ close(IPTABLES); return(1); } if(($rule[0] eq "TARPIT") && ($rule[3] eq $ip)){ close(IPTABLES); return(1); } $i++; } close(IPTABLES); 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 ( uc($var) eq uc("LogFile") ) { $LOG = $value; } if ( uc($var) eq uc("Blacklist") ) { $BLACKLIST = $value; } if ( uc($var) eq uc("Whitelist") ) { $WHITELIST = $value; } if ( uc($var) eq uc("SnortFIFO") ) { $SNORT_FIFO = $value; } if ( uc($var) eq uc("Gateway") ) { $MY_GW_ADDR = $value; } if ( uc($var) eq uc("SyslogLevel") ) { $SYSLOG_LEVEL = $value; } if ( uc($var) eq uc("TriggerLevel") ) { $PRIORITY_TRIGGER = $value; } if ( uc($var) eq uc("IPtables") ) { $IPTABLES = $value; } if ( uc($var) eq uc("IptableChains") ) { @CHAINS = split(/\,/,$value); } if ( uc($var) eq uc("FWactions") ) { @ACTIONS = split(/\,/,$value); } if ( uc($var) eq uc("IProute") ) { $ROUTE = $value; } if ( uc($var) eq uc("IFconfig") ) { $IFCONFIG = $value; } if ( uc($var) eq uc("PublicInterface") ) { $INTERFACE = $value; } if ( uc($var) eq uc("Timeout") ) { $TIMEOUT = $value; } if ( uc($var) eq uc("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("$ACTIONS[0]"){ @ACTIONS="DROP"; } unless("$IFCONFIG"){ $IFCONFIG="/sbin/ifconfig"; } unless("$INTERFACE"){ print "$0: Interface not set"; exit; } unless("$TIMEOUT"){ $TIMEOUT="3600"; } unless("$ALARMTICK"){ $ALARMTICK="60"; } foreach $action (@ACTIONS){ if ( uc($action) eq "TARPIT" ){ $tarpit=1; } if ( uc($action) eq "DROP" ){ $drop=1; } } 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( "-- SyslogLevel= $SYSLOG_LEVEL"); writelog( "-- TriggerLevel= $PRIORITY_TRIGGER"); writelog( "-- IPtables= $IPTABLES "); writelog( "-- IPtableChains= @CHAINS "); writelog( "-- FWactions= TARPIT=$tarpit, DROP=$drop "); 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);