#!/usr/local/bin/perl # (^^^^^^^^^^^^^ Change this to point to your perl, or link it to real perl) # # BrerFox watches over the TCP tarbaby. # # This script is to be spawned by a booby trap in the /etc/hosts.deny # file used by tcpd and similarly protected systems. It logs the # attempted use of a service (succesful or not) and sends warnings or # alerts to the appropriate security administrator via e-mail and/or an # X popup window. (Meanwhile the tarbaby is meant to be "twisted" on to the # process which is probing the selected port, though that is not required.) # # Command line arguments (to be passed from tcpd as %d %a %h %u %p ) # # [flags] daemon addr host user pid [status] # # The flags are: # -a set syslog level to ALERT # -w set syslog level to Warning # -n set syslog level to notice # -F perform reverse finger # -H perform hostname lookup in DNS # -N record netstat output # -C obtain contact information using ipw # -D add hosts.deny entry (instead of just a suggestion!) # -S filename play the sound in 'filename' # -q no e-mail notification (quiet) # -W wait on the line when finished (useful for udp wait) # -T trace the route with traceroute # -t trace route if not found in DNS (requires -H) # -X X-windows notification # -v version # -h help # # The earliest version of BrerFox was based on the Bmon script found # in "Practical Unix & Internet Security" by Simson Garfinkle and # Gene Spafford (O'Reilly & Associates, 1996), and it still # incorporates ideas from that useful source. The idea of a "booby # trap" using tcp_wrappers is described in the hosts_access(5) man # page that comes with tcpd. # # Eric Myers - 12 May 1998 # Department of Physics, University of Michigan, Ann Arbor 48109-1120 # @(#) $Id: BrerFox,v 1.52 2000/03/20 22:40:06 myers Exp myers $ ###################################################################### # Configuration: # # Set these to point to your Security Administrator $SecurityEmail = "root"; $SecurityDISPLAY = "gibbs.physics.lsa.umich.edu:0" ; # Timeouts limit how frequently e-mail/x-alerts are created # (to prevent Denial of Service or simple overload). $timeoutA = 5*60 ; # time before another "alert" $timeoutW = 10*60 ; # time before another "warning" $timeoutN = 15*60 ; # time before another "normal" # Where sound files can be found and how to play them @SoundPath=( "/usr/local/adm", "/usr/local/lib/sounds"); $PlayCmd="/usr/bin/play"; # Finger command: If possible, use the safe_finger command # to avoid retaliation or other problems. $Finger="finger"; if ( -x "/usr/local/adm/safe_finger" ) { $Finger="/usr/local/adm/safe_finger";} if ( -x "/usr/sbin/safe_finger" ) { $Finger="/usr/sbin/safe_finger";} # Mailx command for e-mail notification: this should be a mail # command which understands -s for subject. HP's use mailx, Linux # uses mail. I like mush so also look for that. $Mailx="mail -s"; if ( -x "/usr/bin/mailx" ) { $Mailx="/usr/bin/mailx -s"; } if ( -x "/usr/local/bin/mush" ) { $Mailx="/usr/local/bin/mush -s"; } # Testing: larger numbers are more verbose. $Debug_Level = 7; # End configuration. ###################################################################### # This is just for debugging, ignore it for now: $Debug="/tmp/BrerFox.dbg"; umask(007); open(DEBUG, ">$Debug"); &Debug("$0 run by $ENV{'USER'} at $DATE"); &Debug("$0: Finger command is $Finger"); &Debug("$0: Mailx command is $Mailx"); # Set the PATH explicitly to avoid security problems. # Just use what is needed, nothing more. $ENV{'PATH'} = '/usr/ucb:/usr/sbin:/usr/bin:/bin:/usr/etc:/etc:/usr/bin/X11:/usr/X11R6/bin/:/usr/local/bin'; $ENV{'SHELL'} = '/bin/sh'; $ENV{'IFS'} = ''; umask(037); $PROG="BrerFox"; $MSG="/tmp/" . $PROG . $$. ".msg"; chop($DATE=`/bin/date`); chop($UNAME=`uname`); chop($hostname=`hostname`); ## RCS version info for report and -v output $VERS='$Revision: 1.52 $'; $VERS =~ s/\$//g; $VERS =~ s/Revision: //; #################### ## Process command line arguments require "getopts.pl"; &Getopts('awntCFNHDTS:fqXvhW') || die "Use -h for help. Stopped"; if ( $opt_h ) { print "Allowed options: -a set syslog level to \'ALERT\' -w set syslog level to \'Warning\' -n set syslog level to \'notice\' -t trace the route if not found in DNS (requires -H) -F perform reverse finger -H perform hostname lookup in DNS -N record netstat output -C obtain contact information using ipw -D add hosts.deny entry (instead of just a suggestion) -T trace the route with traceroute -S filename play the sound in \'filename\' -W wait on the line when finished (eg udp wait) -q no e-mail notification (quiet) -X X-windows notification -v version -h help "; exit 1;} if ( $opt_v ) { print "This is $PROG version $VERS, part of the TCP tarbaby.\n"; exit 2; } # Treat -f same as -F, but give a warning $Finger3 = ""; if ( $opt_f ) { $opt_F = $opt_f; $Finger3 = "\nPlease use -F rather than -f\n"; } ## Get the command line argument list ($daemon, $addr, $host, $user, $pid, $status ) = @ARGV; if ( $daemon eq "test" ) { print "$PROG: test mode: number of arguments =",$#ARGV+1,"\n"; print "Arguments: daemon=$daemon, addr=$addr, host=$host, user=$user, pid=$pid, status=$status \n "; &Debug("Arguments: @ARGV"); exit 47;} if ( $#ARGV < 2 ) { `logger -i -t $PROG -p daemon.warning wrong number of arguments.\n`; &Debug("$PROG: wrong number of arguments (",$#ARGV+1,")"); print DEBUG "Arguments: daemon=$daemon, addr=$addr, host=$host, user=$user, pid=$pid, status=$status \n "; exit 1; } #################### # Syslog message priority: # For most things, we simply want a notice (just log it). # Unsuccessful attempts are Warnings (w/ e-mail) # Unsuccessful attempts on special accounts merit an ALERT (X-alert?). $level ="notice"; #if ( $status ne "allowed") { # $level = "Warning"; #} if ( ( $daemon eq "in.rshd" || $daemon eq "in.rlogind" ) && ( $user eq "root" || $user eq "security" ) ) { $level = "ALERT"; } # command line options override priorities chosen above: if ( $opt_n ) { $level = "notice";} if ( $opt_w ) { $level = "Warning";} if ( $opt_a ) { $level = "ALERT";} &Debug( "Priority is $level " ); ################ # Log it to system log: $Message = "${level}: attempted connect to $daemon on $hostname"; if ( $user ne "" && $user ne "unknown" ){ $Message = $Message . " from $user\@$addr "; } else { $Message = $Message . " from ???\@$addr "; } system "logger -i -p auth.$level -t $PROG \"$Message ($user\@$host) - $status\" \n"; ################ # Play a sound, if requested if ( $opt_S ) { &Debug("Sound path is @SoundPath"); &Debug("Play command is $PlayCmd"); while( @SoundPath ) { $SoundDir = shift(@SoundPath); &Debug("Looking for $opt_S in $SoundDir"); if ( -d $SoundDir ) { $SoundFile=$SoundDir . "/" . $opt_S; &Debug("Trying to find sound file $SoundFile"); if ( -r $SoundFile ) { &Debug("Trying to $PlayCmd $SoundFile") ; system "$PlayCmd $SoundFile "; } else { $SoundFile=$SoundFile . ".au"; &Debug("Trying to find sound file $SoundFile"); if ( -r $SoundFile ) { &Debug("Trying to $PlayCmd $SoundFile") ; system "$PlayCmd $SoundFile"; } } } } } # For "notice" then that is all there is if ( $level eq "notice" ) { &wait_exit(0); } # If the IP is 0.0.0.0 there is nothing else we can do if ( $addr eq "0.0.0.0") { &wait_exit(0); } ################ # Lock: Check for existing lock younger than $timeoutX # If it exists, bail. Otherwise, create the lock and go on. &Debug("Checking for a lock file...") ; if ( -w "/var/LOCK" ){ $LOCKDIR="/var/lock"; } else { $LOCKDIR="/tmp"; } $LOCKFILE=$LOCKDIR . "/$PROG-" . $level; &Debug("Lockfile name is $LOCKFILE"); if ( -e $LOCKFILE ) { # lockfile exists? # Stat the file to get the age of the lock: ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks ) = stat($LOCKFILE); $age = time() - $ctime; # Check here for case that previous lock had "unknown" but now # we have better info (not "unknown"). In that case, ignore # the lock and report again (by making lock file seem older). # We get at most 3 reports during the lock period as we get better info. if ( open(LOCK, "<$LOCKFILE") ) { ($L_level, $L_pid, $L_daemon, $L_user, $L_host, $L_addr) = =~ /(\S*)\s*\[(\d*)\]\s*(\S*)\s*(\S*)\@(\S*)\s*(\S*)/; &Debug("Previous lock from $L_user\@$L_host"); if ( ($L_user eq "unknown" && $user ne "unknown") || ($L_host eq "unknown" && $host ne "unknown") ) { $age += $timeoutW + $timeoutA; # enough to make it old } close(LOCK); } # Check the age of the lock: if ( $level eq "Warning" && $age < $timeoutW ) { exit(1);} if ( $level eq "ALERT" && $age < $timeoutA ) { exit(1);} unlink($LOCKFILE); # remove old lock so to add new one }# lockfile exits? &Debug("No current lock, so trying to lock..."); $UMASK=umask(000); # must be able to stomp on a lock file if ( open(LOCK, ">$LOCKFILE") ) { print LOCK "$level [$pid] $daemon from $user\@$host [$addr]\n"; close(LOCK); } else { &Debug("Cannot write to lock file!"); `logger -i -t $PROG -p daemon.warning cannot lock.\n`; exit 7; } umask($UMASK); # back to normal ################# # DENY: entry for /etc/hosts.deny file $DenyMSG=""; &Debug("Trying to read /etc/hosts.deny..."); if( ! open(DENY, "/etc/hosts.deny") ) { &Debug("Cannot open or read /etc/hosts.deny file!"); $DenyMSG="Cannot open or read /etc/hosts.deny file!\n"; } else { # First check that an entry does not already exist &Debug("Looking for $addr in /etc/hosts.deny..."); while() { (($DenyAddr) = /^ALL:\s(\S+)/) ; last if ( $DenyAddr eq $addr ) ; } close DENY; if ( $DenyAddr eq $addr ) { &Debug("Found it! $_ "); $DenyMSG = "Access from this host is already blocked:\n"; $DenyMSG .= "$_\n"; } else { if ( $opt_D && $host ne "unknown" ) { $DenyEntry = "# $DATE - $daemon by $user\@$host\n"; $DenyEntry .= "ALL: $addr "; if ( $addr ne $host ) { $DenyEntry .= " $host "; } $DenyEntry .= "\n"; &Debug("trying to write to /etc/hosts.deny..."); if ( ! open (DENY ,">>/etc/hosts.deny") ) { &Debug("cannot write to /etc/hosts.deny"); `logger -i -t $PROG -p daemon.warning cannot write to /etc/hosts.deny\n`; $DenyMSG = "Cannot write to /etc/hosts.deny file!"; $DenyMSG .= " Suggested entry:\n" . $DenyEntry; } else { print DENY $DenyEntry; close DENY; $DenyMSG = "Added entry to /etc/hosts.deny file:\n" . $DenyEntry; } } } } ## if -q was given then that is all we can do, no reporting if ( $opt_q ) { &wait_exit(0); } ############# # Reverse finger them to see what we can see $Finger1=""; $Finger2=""; if ( $opt_F ) { if ( $addr ne "" && $addr ne "unknown" ) { $Finger1=`$Finger \@$addr`; } if ( $user ne "" && $user ne "unknown" ){ $Finger2=`$Finger -l $user\@$addr`; } } ############# # Netstat to see all connections to this host if ( $opt_N || $addr eq "unknown" ) { if ( $UNAME eq "Linux" ) { $Netstat=`netstat -n -A inet`; } else { $Netstat=`netstat -n -f inet`; } } ############ # Note: getting the address/status info below may take some time, # so it's all collected together below this point. The stuff above # is intended to run as quickly as practicle to get the status of # of connections at the time of the hit. ############ ############# # Do DNS gethostnamebyaddr here if we have an address if ( $opt_H ) { if ( $addr && $addr ne "unknown" ){ $AF_INET = 2; $IPaddr = pack('C4', split( /\./, $addr ) ); ( $host, $aliases, $addrtype, $length, @addr ) = gethostbyaddr( $IPaddr, $AF_INET ); if ( $host eq "" ) { $host = $addr ;} } } ############# # Contact info: use ipw to determine contact info if ( $opt_C && $addr ne "unknown" ) { $Contact = `ipw $addr`; if ( $Contact =~ /not found/ ) { $contact .= "\nYou can download ipw from http://e-scrub.com/ipw/ \n"; } } ############# # Traceroute to see where they are if ( $opt_T ) { $Traceroute=`traceroute $addr`; } else { if ( $opt_t && $opt_H && $host eq $addr && $addr ne "unknown" ) { $Traceroute=`traceroute $addr`; } } ################################################ # Construct Report open(MSG, ">$MSG"); print MSG "\n$Message \n"; print MSG "\nLogged on $DATE by $PROG $VERS \n"; print MSG "\nAddress info:\n$addr $host $aliases \n"; $Divider = "\n==========================================\n"; if ( $Finger1 ne "" ) { print MSG $Divider, "Finger \@$host yields: \n\n"; print MSG "$Finger1\n\n"; } if ( $Finger2 ne "" ) { print MSG $Divider, "Finger $user\@$host yields: \n\n"; print MSG "$Finger2\n"; print MSG "$Finger3\n" } if ( $DenyMSG ne "" ) { print MSG $Divider, $DenyMSG; } if ( $Netstat ne "" ) { print MSG $Divider, "Output from netstat:\n\n"; print MSG "$Netstat\n\n"; } if ( $Contact ne "" ) { print MSG $Divider, "Contact information via ipw:\n\n"; print MSG "$Contact\n\n"; } if ( $Traceroute ne "" ) { print MSG $Divider, "Output from traceroute:\n\n"; print MSG "$Traceroute\n\n"; } ################## # Send to security administrator (assumes Mailx will take -s flag) close MSG; `$Mailx "$Message" root <$MSG`; system "/bin/rm -f $MSG"; # cleanup ################## # X-alert: will pop up a window on the security admin's console if ( $opt_X && $level eq "ALERT" ) { `xmessage -display $SecurityDISPLAY -timeout $timeoutA "$Message" `; } ################## # Cleanup / Exit: &wait_exit(0); sub wait_exit { &Debug("waiting to exit."); close DEBUG; if ( $opt_W ) { # this is dumb, why wait? -EAM if ( $level eq "Warning" ) {sleep( $timeoutW );} if ( $level eq "ALERT" ) {sleep( $timeoutA );} } exit($_[0]); } ################## # Debug messages: sub Debug { if ( $_[0] =~ m/^\d+$/ && $_[0] > 0 ) { my $level = $_[0]; shift;} else { # default if there is no debug level my $level = 5; } if ( *DEBUG && $Debug_Level && $level <= $Debug_Level ) { print DEBUG "% @_ \n"; } else { `logger -i -t $PROG -p daemon.warning Cannot write to debug file: level=$level, Threshold=$Debug_Level \n`; } }