Using iptables to beat the SSH crackers
Posted: 30 August 2006 at 22:14:27
At Iodynamics, we manage many, many Linux servers and some of these servers need SSH open to all. As a result, we see a lot of brute-force dictionary attacks attempted against these servers. Most of the time, the attacks are not successful. Very rarely, however, the attacks do result in user-level access (we never allow root to log in via SSH).
<p>For a long time now, we’ve used a handy-dandy tool called <a target="_new" href="http://denyhosts.sourceforge.net/">DenyHosts</a> to deal with SSH attacks. DenyHosts works by watching your system log files (i.e. /var/log/secure) for stuff like this: </p> <pre>Aug 29 10:19:25 mail sshd[5367]: Failed password for invalid user pirate from ::ffff:204.15.225.74 port 51436 ssh2 <br />Aug 29 10:19:26 mail sshd[5376]: Invalid user aqua from ::ffff:204.15.225.74 <br />Aug 29 10:19:27 mail sshd[5369]: Failed password for invalid user pirate from ::ffff:204.15.225.74 port 50077 ssh2 <br />Aug 29 10:19:27 mail sshd[5370]: Failed password for invalid user pirate from ::ffff:204.15.225.74 port 34810 ssh2 <br />Aug 29 10:19:27 mail sshd[5378]: Invalid user aqua from ::ffff:204.15.225.74 <br />Aug 29 10:19:27 mail sshd[5379]: Invalid user aqua from ::ffff:204.15.225.74 <br />Aug 29 10:19:29 mail sshd[5376]: Failed password for invalid user aqua from ::ffff:204.15.225.74 port 52053 ssh2 <br />Aug 29 10:19:29 mail sshd[5382]: Invalid user cypher from ::ffff:204.15.225.74 <br />Aug 29 10:19:29 mail sshd[5378]: Failed password for invalid user aqua from ::ffff:204.15.225.74 port 50720 ssh2</pre> <p>DenyHosts then identifies the IP address from which the attack originates (in this case, 204.15.225.74) and adds a line to the TCP Wrappers hosts.deny file for sshd:</p> <pre>sshd: 204.15.225.74</pre> <p>DenyHosts is a python script that must be run regularly by cron. The convention is to run it every 5 or 10 minutes.</p> <p>The big problem with DenyHosts is that it still lets an attacker try to break into your system for up to 5 or 10 minutes. </p> <p>In its defense, DenyHosts has become more sophisticated over time. It now supports a centralized repository of blacklisted IPs. As a result, you can share information about attacking IP addresses with other DenyHosts users and you can benefit by denying SSH access to IP addresses other DenyHosts sites have discovered. This is called <em>synchronization mode</em> in the DenyHosts configuration file. I haven’t tried it, but, in theory, it sounds like a smart way to cover your bases.</p> <p>It seems there are a few tools out there that do something similar to DenyHosts. One that looks very interesting is <a target="_new" href="http://www.csc.liv.ac.uk/%7Egreg/sshdfilter/">sshdfilter</a>. This program is a wrapper around the SSH daemon itself and will add entries to the hosts.deny file in near real-time.</p> <p>In my search for something better than DenyHosts, I came across <a target="_new" href="http://olivier.sessink.nl/publications/blacklisting/index.html">this page</a> by Oliver Sessink which describes a countermeasure strategy implemented completely in iptables.</p> <p>This intrigued me, so I went about implementing Oliver’s example code in a way that could be used in the /etc/sysconfig/iptables file found on Red Hat and Fedora Core systems. The result is shown below with the new stuff in bold:</p> <pre>*filter <br />:INPUT ACCEPT [0:0] <br />:FORWARD ACCEPT [0:0] <br />:OUTPUT ACCEPT [0:0] <br /><strong>:properREJECT - [0:0] <br /></strong>:blacklistdrop - [0:0] <br />:RH-Firewall-1-INPUT - [0:0] <br />-A INPUT -j RH-Firewall-1-INPUT <br />-A FORWARD -j RH-Firewall-1-INPUT <br /><strong>-A properREJECT -p tcp -j REJECT --reject-with tcp-reset <br /></strong>-A properREJECT -j REJECT --reject-with icmp-port-unreachable <br />-A blacklistdrop -j LOG --log-prefix "BLACKLISTING: " <br />-A blacklistdrop -m recent --name BLACKLIST --set -j DROP <br />-A RH-Firewall-1-INPUT -m recent --name BLACKLIST --update --seconds 120 -j DROP <br /># Other rules you might have go here ... <br /><strong># *new* ssh connections are all put into a list 'sshconn', If there are <br /></strong># 3 such packets within 30 seconds we send the package to chain 'blacklistdrop' <br />-A RH-Firewall-1-INPUT -p tcp --dport 22 -m state --state NEW -m recent --name sshconn --rcheck --seconds 30 --hitcount 3 -j blacklistdrop <br /># If we have seen less then 3 such packets in the last 30 seconds we accept <br />-A RH-Firewall-1-INPUT -p tcp --dport 22 -m state --state NEW -m recent --name sshconn --set -j ACCEPT <br /># If the destination address is in the blacklist, we REJECT *any* packet <br />-A RH-Firewall-1-INPUT -m recent --name BLACKLIST --rdest --rcheck --seconds 30 -j properREJECT <br /># REJECT everything else <br />-A RH-Firewall-1-INPUT -j REJECT --reject-with icmp-host-prohibited <br />COMMIT </pre> <p>I’ve been running this on a few systems now for a couple of days and it seems to work very well. When an IP is blacklisted, a line is written to your system messages log that looks like this:</p> <p>Aug 29 18:29:26 tic kernel: BLACKLISTING: IN=eth1 OUT= MAC=00:90:27:18:9d:5a:00:50:bf:06:32:24:08:00 SRC=166.70.238.251 DST=166.70.238.250 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=14472 DF PROTO=TCP SPT=51190 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0</p><p>If you have the leeway to disallow all password-based authentication into a system and only use DSA and/or RSA keys, that is a terrific solution as well as it renders these types of SSH attacks as useless.<br /></p>