#!/bin/sh # ## Copyright (C) 2006, Ryan MacDonald ## 2006, R-fx Networks # # email for alerts email="" # path to route command route_cmd="/sbin/route" # 0 = disabled, 1 = enabled route_ban="0" # port:trigger ptrig="80:200 443:200 110:50 25:50 26:50 143:50 465:50" # white space or comma seperate list too ignore files # the file /root/.fg_ipignore is a static ip ignroe file, line seperated igfiles="/etc/apf/allow_hosts.rules /etc/apf/deny_hosts.rules /root/.fg_ipignore /etc/csf/csf.deny" # path to iptables firewall script if [ -f "/etc/apf/apf" ]; then ipt_cmd="/etc/apf/apf" elif [ -f "/usr/sbin/csf" ]; then ipt_cmd="/usr/sbin/csf" else echo "apf or csf not found, falling back to route table bans..." route_ban="1" fi # path to fguard log fglog=/var/log/fguard.log # temporary file location for netstat output ntl=/tmp/.fg.ntcache.$$ # fetch hostname hname=`hostname` # unix time for lock tracking utime=`date +"%s"` # lock file path lock="/root/.fg.lock.utime" # lock file timeout in seconds lock_timeout="300" #################################### #################################### igtmp=/tmp/.fg_igtmp.$$ rm -f /root/.fg_ig* /root/.fg.nt* /tmp/.fg* eout() { # call arg 1 with value for string to be logged output=$1 # call arg 2 with value 1 for logging too $fglog (eout "text" 1) logt=$2 if [ ! "$output" == "" ] && [ "$logt" == "" ]; then echo "$(date +"%b %d %H:%M:%S") fguard($$): $output" elif [ ! "$output" == "" ] && [ "$logt" == "1" ]; then echo "$(date +"%b %d %H:%M:%S") $(hostname -s) fguard($$): $output" >> $fglog fi } get_state() { # lock routine to prevent toe-stepping from multiple instances if [ -f "$lock" ]; then oval=`cat $lock` diff=$[utime-oval] if [ "$diff" -gt "$lock_timeout" ]; then echo "$utime" > $lock eout "cleared stale lock file file" 1 eout "cleared stale lock file file" else eout "locked subsystem, already running ? ($lock is $diff seconds old) - aborting" 1 eout "locked subsystem, already running ? ($lock is $diff seconds old) - aborting" exit 1 fi else echo "$utime" > $lock fi } clfiles() { rm -f $igtmp $ntl touch $igtmp chmod 600 $igtmp if [ ! -f "/root/.fg_ipignore" ]; then touch /root/.fg_ipignore chmod 600 /root/.fg_ipignore fi if [ ! -f "$ntl" ]; then touch $ntl chmod 640 $ntl* chown root:wheel $ntl* else chmod 640 $ntl* chown root:wheel $ntl* fi if [ "$1" == "1" ]; then rm -f $ntl* rm -f $lock fi if [ ! -f "$fglog" ]; then touch $fglog chmod 640 $fglog chown root:wheel $fglog else chmod 640 $fglog chown root:wheel $fglog fi rm -f $igtmp $ntl } # clear temp files and create any required clfiles # loop through the ignore files, strip out #'s and place into a single file for i in `echo $igfiles`; do if [ -f "$i" ]; then cat $i | grep -vE '\#|^$|:|/' | grep -E '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' >> $igtmp fi done # local addresses into ignore file /sbin/ifconfig | grep -w inet | cut -d : -f 2 | cut -d \ -f 1 | tr ' ' '\n' | egrep '[0-9]' | egrep -v "^$" >> $igtmp echo "0.0.0.0" >> $igtmp nice -n 19 /bin/netstat -nt | grep -w "tcp" | grep -v "LISTEN" | sed 's/::ffff://' > $ntl 2> /dev/null stats() { port=$1 if [ -z "$port" ]; then echo "Top 10 Connections by IP" cat $ntl | awk '{print$5}' | tr ':' ' ' | awk '{print$1}' | grep -vwf $igtmp | grep -E '[0-9]' | sort -n | uniq -c | sort -n | tail -n 10 | tac echo && echo "Top 10 Connections by Port" cat $ntl | awk '{print$4}' | tr ':' ' ' | awk '{print$2}' | grep -vwf $igtmp | grep -E '[0-9]' | sort -n | uniq -c | sort -n | tail -n 10 | tac echo && echo "Connection States" cat $ntl | grep tcp | grep -v LISTEN | awk '{print$6}' | sort | uniq -c | sort -n elif [ "$port" ]; then echo "Top 10 Connections by IP on Port $port" cat $ntl | grep -w "$port" | awk '{print$5}' | tr ':' ' ' | awk '{print$1}' | grep -vwf $igtmp | grep -E '[0-9]' | sort -n | uniq -c | sort -n | tail -n 10 | tac echo && echo "Connection States on Port $port" cat $ntl | grep -w "$port" | grep tcp | grep -v LISTEN | awk '{print$6}' | sort | uniq -c | sort -n fi } fg() { # check status of connections against each port for chk in `echo $ptrig`; do port=`echo $chk | tr ':' ' ' | awk '{print$1}'` trig=`echo $chk | tr ':' ' ' | awk '{print$2}'` if [ "$port" ]; then portg=":$port" else portg=":" fi cat $ntl | grep $portg | awk '{print$5}' | tr ':' ' ' | awk '{print$1}' | grep -vwf $igtmp | grep -E '[0-9]' | sort -n | uniq -c | sort -n | awk -v t=$trig '$1>=t' > $ntl.run # audit amount of connections from each ip agains port for audit in `cat $ntl.run | awk '{print$1"%"$2}'`; do cnt=`echo $audit | tr '%' ' ' | awk '{print$1}'` host=`echo $audit | tr '%' ' ' | awk '{print$2}'` if [ "$host" ]; then # if already banned - ignore if [ -f "/etc/apf/deny_hosts.rules" ]; then banchk=`grep -ri $host /etc/apf/deny_hosts.rules` elif [ -f "/etc/csf/csf.deny" ]; then banchk=`grep -ri $host /etc/csf/csf.deny` else banchk="" fi # ban anyone connected above the $trig value for a port if [ -z "$banchk" ] && [ "$cnt" -ge "$trig" ]; then eout "connection flood to port $port banned from $host - $cnt connections" 1 eout "connection flood to port $port banned from $host - $cnt connections" if [ -f "$ipt_cmd" ]; then $ipt_cmd -d $host "fguard ban: $cnt connections to port $port" >> /dev/null 2>&1 fi if [ "$route_ban" == "1" ]; then $route_cmd add -host $host reject fi if [ "$email" ]; then # send email alert echo "fguard monitor on $hname detected a connection based flood from $host connected to port $port with $cnt connections, the attacking host has been banned." | mail -s "fguard alert from $hname" $email fi fi fi done done } case "$1" in -v|-s|--verbose|--stats) stats $2 clfiles 1 exit 0 ;; *) get_state fg clfiles 1 esac exit 0