I used to employ a firewall rule that would detect spurious connects on port 22 (ssh) and place both the offending IP on a badguys table
and flush the state table for said IP (Meaning, anything connected with that source IP was no longer treated as connected by the firewall). A cron job would then cleanup old entries from the badguys table. For years, this worked remarkably! My auth log would show 3 attempts and then activity from the source ceased. Unfortunately, the bot nets got smarter. I started seeing several hundreds of bots attempting a password every several minutes which isn't
spurious. As a consequence, my auth log would be flooded with annoying password failures! So, I opted to block
everything except address blocks of known locations (e.g. cafe, work, school, etc...)
on top of the spurious connect rule. This works well most of the time but did not fare so well with friends' or acquaintances' houses, airports, or new public WiFi locations in general (which I use my SSH server as a secure tunnel). As I'm to travel soon, the thought of dynamic DNS came to mind. Simply keep a list of dynamic domains and have a cron job resolve each one and add it to the goodguys table (and delete the previous resolutions if they differ). I don't claim it's a new idea, but it's certainly ANOTHER solution.
Too Long Didn't Read- Block ALL incoming (at least) SSH connections with your firewall.
- Add an exception rule for incoming SSH connections for any address on the goodguys table.
- Make a Dynamic DNS hostname (e.g. no-ip.com) for each trusted mobile computer.
- Install a Dynamic Update Client on each trusted mobile computer.
- Add a cron job (every 5 minutes) that resolves a list of dynamic hostnames and adds their corresponding IPs to the goodguys table (preferably deleting old different resolutions).
- Enjoy boiled peanuts (Very necessary, it won't work otherwise).
Here's an example of a Bourne shell script to do step 5 (for pf(4) on FreeBSD) (an updated version can be found
here)
#!/bin/sh
GOODGUYS=/root/goodguys.txt
TABLE=goodguys
while read host
do
hostfile="/tmp/goodguys.${host}"
oldip=""
if [ -s "${hostfile}" ]
then
read oldip < ${hostfile}
fi
str=`host -t A $host 2>/dev/null`
if [ $? -eq 0 ]
then
ip=`echo "${str}" | awk '{ print $NF; exit }'`
echo "${ip}" > ${hostfile}
else
ip=""
rm -f ${hostfile}
fi
if [ -n "${oldip}" -a "${oldip}" != "${ip}" ]
then
/sbin/pfctl -t $TABLE -T del ${oldip} > /dev/null 2>&1
fi
if [ -n "${ip}" ]
then
/sbin/pfctl -t $TABLE -T add ${ip} > /dev/null 2>&1
fi
done < $GOODGUYS
This reads a list of hostnames from
/root/goodguys.txt and adds each resolved address to the table
goodguys and stores the result in
/tmp for later use. If the previous address is different than the current resolved address, the previous address is deleted from the
goodguys table.
Place it in, say
/root/bin/goodguys.shAnd here's what you'd add to
/etc/crontab to do this every 5 minutes:
#minute hour mday month wday who command
*/5 * * * * root /root/bin/goodguys.sh
Voila! I find this to be the best solution of all the bruteforce solutions. Everyone is blocked except for you and friends ... granted you and your friends are using their own machines. No special software or firewall rules needed and most importantly
NO ANNOYING AUTH LOG FAILURE MESSAGES!Caveats- It may take several minutes before you can ssh to your server.
- If your dynamic hostname expires, said hostname may resolve to the dynamic DNS provider.