Article ID 20041012A
Subject FreeBSD sshd watchdog script
Concept Simple script to firewall attempted ssh logins
OS FreeBSD

About six weeks ago, I started noticing a small number of attempted ssh logins to one of my systems using some generic logins. As the weeks progress, the number of attempted logins had increased at an alarming rate. Initially I was manually adding ipfw (IP firewall) rules, then came up with this simple "watchdog" system.

The initial plan for the script was to scan /var/log/messages every couple of minutes via crontab for entries that look something like:
Oct  9 22:11:19 vulcan sshd[5893]: Illegal user admin from xxx.xxx.xxx.xxx
I'll awk out the offending IP address, check to see if it already exists in our ipfw rulesets, and if the address does not exist, then I add a new rule blocking the offending IP address. I will also add some basic logging so I know when the rule was added. So the initial shell script looked like:
#!/bin/tcsh
foreach PROBE (`grep Illegal /var/log/messages | awk '{print $10}' | sort | uniq`)
	@ STAT = `/sbin/ipfw show | grep "$PROBE" | wc -l`
	if ( $STAT == 0 ) then
		/sbin/ipfw add 107 deny ip from $PROBE to me
		echo "`date` - $PROBE" > /var/log/ssh_watchdog.log
	endif
end
I then add a cron entry to call this script every five minutes. This is not real time, but should stop potential brute force attacks. It then occurred to me that there is a potential to accidently lock my self out. So I modify the grep line to ignore a "safe" IP address,
foreach PROBE (`grep Illegal /var/log/messages | awk '{print $10}' | grep -v xxx.xxx.xxx.xx | sort | uniq`)
This seems to do the trick quite nicely, but if the messages logfile gets too big before newsyslog rotates it, you can use valuable resources scanning a big log file every couple of minutes. I initially thought I could just use "nice" to prevent this, but I think the better idea is to edit syslogd's config file so that all sshd messages go to its own log. This way I am scanning messages that are related to only the secure shell daemon. I add the following lines to /etc/syslogd.conf
!sshd
*.*                                             /var/log/sshd.log
And I want to rotate the sshd.log nightly and keep 20 days worth of logs, so I add this line to my /etcsyslog.conf
/var/log/sshd.log                       600  20    *    @T00  Z
Now that all sshd messages go to /var/log/sshd.log, I need to modify my script and change the foreach statement to look at that file instead of /var/log/messages. I realized that if I ever needed to reboot the system, all of these rules would be lost, so I added code that would write the rules to a script that resides in /usr/local/etc/rc.d so they get added back in on system startup. First I need to create a base script, which I will call firewall_107.sh
#!/bin/sh
# ipfw "107" rules (attempted ssh logins)
# rules added by sshd_watchdog script
You will need to make the file executable in order for FreeBSD to process it. Now that the base script is created, I then add some code to add rules to the firewall_107.sh script:
        @ STAT = `grep "$PROBE" /usr/local/etc/rc.d/firewall_107.sh | wc -l`
        if ( $STAT == 0 ) then
                echo "/sbin/ipfw -q add 00107 deny ip from $PROBE to me" >> /usr/local/etc/rc.d/firewall_107.sh
        endif
In the above code, I check to see if the rule exists in the firewall_107.sh and if not, add the appropriate rule. Thats pretty much it. You can tune the frequency of checks by changing the calls to crontab. The final script looks like:
#!/bin/tcsh
#
# Check sshd.log for attempted logins and firewall the offending
# ip address if it does not already exist in current ipfw rules.
# Also add the rule to /usr/local/etc/rc.d/firewall_107.sh so
# rules get enabled during system startup.

foreach PROBE (`grep Illegal /var/log/sshd.log | awk '{print $10}' | grep -v xxx.xxx.xxx.xxx | sort | uniq`)
        @ STAT = `/sbin/ipfw show | grep "$PROBE" | wc -l`
        if ( $STAT == 0 ) then
                /sbin/ipfw add 107 deny ip from $PROBE to me
                echo "`date` - $PROBE" > /var/log/ssh_watchdog.log
        endif

        @ STAT = `grep "$PROBE" /usr/local/etc/rc.d/firewall_107.sh | wc -l`
        if ( $STAT == 0 ) then
                echo "/sbin/ipfw -q add 00107 deny ip from $PROBE to me" >> /usr/local/etc/rc.d/firewall_107.sh
        endif
end

Some additional notes/thoughts
I am by no means an expert in security. I would not consider this script as a first level defense on secure shell attacks. Any systems that are exposed directly to the internet should have the "root" login disabled (default in most sshd configs) by setting "PermitRootLogin no" in your sshd_config file. Any generic accounts should be locked down or better yet, specify that only specific users be allowed access to secure shell. "man ssh" for more details. It also does not hurt, and generally good practice, to add firewall rules to deny ssh access to all but specific addresses. In some situations, this is not practical, so this is where the above script becomes handy. Questions/Comments welcome.



©2004-2020 Paul Boehmer