Here’s how to create a fail-to-ban type of functionality on an EdgeRouter completely using BASH, without installing any 3rd party packages. We are going to create a single script and add a scheduled job to run it. That’s all there is to it.
Step 1
Run the following
vi /config/scripts/fail-to-ban
Now we need to turn off auto indent before copying and pasting the script below. Type the following:
:set noai i
Now paste the following script into the file:
#!/bin/bash PATH=$PATH:/usr/bin:/bin:/sbin:/usr/sbin ATTEMPTS=10; # NUMBER OF ATTEMPTS IN A GIVEN INTERVAL INTERVAL=600; # INTERVAL (IN SECONDS) TO WATCH FOR FAILED ATTEMPTS - HISTORICALLY FROM CURRENT TIME PERMBAN=100; # AFTER THIS NUM OF FAILED ATTEMPTS, BAN UNTIL LOG ROTATES BLOCKSECS=3600; # AFTER THIS TIME (IN SECONDS), UNBLOCK A BLOCKED IP BLOCKED_ALREADY="" BLOCKED_NOW="" SKIPPED="" EXPIRED_BLOCK="" NOW=`date '+%s'` isip() { ISIP=$1 if [ $(echo $IP | sed 's/[^.]//g' | awk '{print length; }' 2> /dev/null) -eq 3 ]; then ISIP=1 fi } fail2ban() { # echo failing $IP with count $COUNT and lastcount $LASTCOUNT IP=$IP EXISTS=`nice -19 iptables -n -L | grep $IP | wc -l` IS_LOCAL=`echo $IP | grep -E '^10\.|192\.168|127\.' | wc -l` if [ $EXISTS -gt 0 ]; then BLOCKED_ALREADY+=",$IP:$COUNT" # echo "IP $IP is already blocked" elif [ $IS_LOCAL -eq 1 ]; then SKIPPED+=",$IP:$COUNT" # echo "IP is local IP. Not blocking" else if [ ! "$IP" == "" ]; then # echo "Blocking IP $IP after $COUNT abuses." BLOCKED_NOW+=",$IP:$COUNT" iptables -I INPUT 1 -j DROP -s $IP echo "`date`:$IP:$NEWCOUNT:$COUNT:BLOCKED" >> /tmp/banned.log fi fi } updateList() { NOW=`date '+%s'` sed -i /tmp/ip-list.log -e "s/:"$IP":"$LASTCOUNT".*$/:"$IP":"$COUNT":"$NOW"/" } updateTime() { NOW=`date '+%s'` sed -i /tmp/ip-list.log -e "s/:"$IP":"$LASTCOUNT".*$/:"$IP":"$LASTCOUNT":"$NOW"/" } showList() { LIST="$2" DESCRIPTION="$1" if [ ! "$LIST" == "" ]; then echo "$DESCRIPTION" for i in `echo "$LIST"` do BIP=$(echo $i | sed -e 's/:.*$//') BCOUNT=$(echo $i | sed -e 's/^.*://') if [ ! "$BIP" == "" ]; then echo $BIP $BCOUNT fi done fi } checkExpired() { BLOCKED=$(nice -19 iptables -L INPUT -n | grep "^DROP" | sed -e 's/^.*-- //' | sed -e 's/ .*$//') for i in `grep -e "$BLOCKED" /tmp/ip-list.log` do IP=`echo $i | cut -d':' -f2` isip $IP COUNT=`echo $i | cut -d':' -f3` LASTACTION=`echo $i | cut -d':' -f4` if [ $((NOW-LASTACTION)) -gt $BLOCKSECS ] && [ ! "$IP" == "" ] && [ $ISIP -eq 1 ] && [ $COUNT -lt $PERMBAN ]; then LINE=`nice -19 iptables -L -n --line-numbers | grep "$IP" | cut -d' ' -f1` if [ ! "$LINE" == "" ]; then echo "Removing block on $IP" echo "$(date):$IP:UNBLOCKED" >> /tmp/banned.log # EXPIRED_BLOCK+=",$IP" echo iptables -D INPUT $LINE iptables -D INPUT $LINE fi fi done } if [ ! -f /tmp/ip-list.log ]; then touch /tmp/ip-list.log fi # CLEANUP - KEEP ONLY HACKERS FROM TODAY echo -n "" > /tmp/ip-list.new IFS=" " for i in `grep "^$(date +%Y%m%d):" /tmp/ip-list.log` do if [ ! "$i" == "" ]; then echo $i >> /tmp/ip-list.new fi done mv /tmp/ip-list.new /tmp/ip-list.log # Do some checking to see if the logs actually changed if [ -f /tmp/this-run ]; then mv /tmp/this-run /tmp/last-run else touch /tmp/last-run fi ls -1 --full-time /var/log/auth.log > /tmp/this-run CHANGE=$(diff /tmp/last-run /tmp/this-run | wc -l) if [ $CHANGE -eq 0 ]; then echo "No change since last run" checkExpired exit fi IPLIST=`nice -19 grep failure /var/log/auth.log | grep rhost | sed -e 's/^.*rhost=//' | sed -e 's/ .*$//' | sort | uniq -c | sed -e 's/^ *//' | sed -e 's/ /:/' | grep -E ":[0-9.]*$" | sed -e "s/^\(.*\)$/$(date +%Y%m%d):\1/"` for i in `echo "$IPLIST"` do #echo $i COUNT=`echo $i | cut -d':' -f2` IP=`echo $i | cut -d':' -f3` DATE=`echo $i | cut -d':' -f1` isip $IP LASTCOUNT=`cat /tmp/ip-list.log | grep ":$IP:" | cut -d':' -f3` ELAPSED=`cat /tmp/ip-list.log | grep ":$IP:" | cut -d':' -f4 | sed -e 's/\n//g'` ELAPSED=$((NOW-ELAPSED)) if [ "$COUNT" == "" ]; then COUNT=0 fi if [ "$LASTCOUNT" == "" ]; then LASTCOUNT=0 fi NEWCOUNT=$((COUNT-LASTCOUNT)) if [ ! "$LASTCOUNT" == "" ] && [ $LASTCOUNT -eq 0 ] && [ $ISIP -eq 1 ]; then echo "$DATE:$IP:$COUNT:$NOW" >> /tmp/ip-list.log # echo "Adding $IP to the IP tracking log with count $COUNT" fi # echo "IP:$IP NEWCOUNT:$NEWCOUNT LASTCOUNT:$LASTCOUNT COUNT:$COUNT ELAPSED:$ELAPSED ISIP:$ISIP ATTEMPTS:$ATTEMPTS INTERVAL:$INTERVAL" if [ $NEWCOUNT -ge $ATTEMPTS ] && [ $ISIP -eq 1 ] && ( [ $ELAPSED -le $INTERVAL ] || [ $COUNT -gt $PERMBAN ] ); then if [ $LASTCOUNT -ne 0 ]; then echo "Updating IP:$IP with NEWCOUNT:$NEWCOUNT ATTEMPTS:$ATTEMPTS ELAPSED:$ELAPSED INTERVAL:$INTERVAL ISIP:$ISIP" updateList fi fail2ban elif [ $NEWCOUNT -ge $ATTEMPTS ] && [ $ISIP -eq 1 ]; then echo "Updating the timestamp for IP $IP; +$NEWCOUNT since last update" updateTime fi done checkExpired IFS="," showList "Blocked | Attempts" "$BLOCKED_ALREADY" showList "Newly Blocked | Attempts" "$BLOCKED_NOW" showList "Skipped | Attempts" "$SKIPPED" showList "Expired" "$EXPIRED_BLOCK"
Now you need to exit and save your changes.
esc :wq
Now let’s make the script executable:
chmod a+x /config/scripts/fail-to-ban
And lastly, we need to add a job to run this periodically and also disable hostname lookups in SSH to make the script’s IP validation work properly and remove a possible DoS vector.
configure set system task-scheduler task failtoban executable path /config/scripts/fail-to-ban set system task-scheduler task failtoban interval 1m set service ssh disable-host-validation commit save
Done! Now your EdgeRouter should be protected against brute force login attacks.