Stopping brute-force ssh scans the easy way

December 1, 2005

I recently stumbled over this blog entry on a nice, easy way to stop brute force ssh scans, so it's time to spread this knowledge around.

If you've got an ssh daemon exposed to the Internet, you know about the brute force ssh scans and password guessing attacks. The real problem with them is the sheer volume, which creates log clutter (and system load) as they spew login failures and unknown users all over your logs.

(If brute force ssh attacks give you security ulcers, get passwords that follow even basic password security rules, because the scanners pretty much only try obvious simple passwords. Anyone who gets compromised by one should be hideously embarrassed.)

In theory the easy way to stop them is to block ssh access from all the networks that don't need it (even tcpwrappers will do; a few 'connection refused' and the scanners go away). Unfortunately, figuring what such places are (or aren't) can be a slog, and it can change over time, and your users can lynch you when you get it wrong.

Thus the easy way to stop brute force ssh scans is to rate-limit incoming ssh connections; this keeps the log spam down to a couple of entries. Linux iptables can do this using the recent and state match modules, like so:

# iptables -A INPUT -p tcp --dport 22 -s <good-IP> -j ACCEPT

[repeat for all of the netblocks and IPs you want know you want to accept ssh from all the time.]

# iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
# iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 3 --rttl --name SSH -j DROP

(This blocks people after 3 new ssh connections within 60 seconds. If you prefer different numbers, adjust --seconds and --hitcount to taste.)

Both lines are necessary because you can only give one of --set or --update, and --update does nothing if the source IP address is not already being tracked by the recent module.

You can see the state of this ruleset in /proc/net/ipt_recent/SSH (or whatever name you gave it). Entries can be removed by writing '-IP.AD.DR.ES' into the file, and the entire thing can be purged by writing 'clear'. The ruleset only keeps stuff on the most recent 100 IP addresses, which shouldn't normally be a problem (this is controlled by the kernel module ipt_recent's ip_list_tot module parameter).

Documentation on the recent matcher is here. It is not yet in the iptables manpage, at least in the versions in Fedora Core 4 and Debian Sarge.

Naturally, the kernel side of this has to be built for your kernel, which is yet another reason to always enable NAT et al in your kernel configuration. (I failed to do this on the system where the brute force scanners most annoy me, thereby insuring they will be annoying me for a while more yet.)

(It turns out that I am late to this particular party; people have been talking about this since at least February of 2005.)

Updated December 16th: corrected the iptables usage; my original version had iptables -I instead of iptables -A, with entertaining havoc ensuing.

Comments on this page:

From at 2005-12-02 09:09:58:

DenyHosts is also a great way to block ssh scanning... It's a script that scans the logfiles for a specified number of failed login attempts, and then it adds the offender to the hosts.deny file (with an optional expiry date)... If you're expecting a large volume of legit ssh logins, then perhaps a hybrid approach of iptables limiting the short term attempts, and DenyHosts limiting the longer term attempts would be preferable.

By cks at 2005-12-02 17:14:09:

Scripts like DenyHosts have a few too many moving parts for me to be comfortable running them; I keep wondering about 'what if something goes wrong?'. (Nothing should go wrong, but worrying is one of my traits.)

For me, the iptables based approach gives a nice peace of mind, precisely because it isn't flying around parsing logfiles and running external programs and writing files and so on.

From at 2005-12-21 14:26:22:

Here's an incantation for doing the same in pf (though without automatic expiry, and with an additional limit of 10 concurrent connections).

    outside_if = fxp0
    table <brutes> persist
    block drop in quick from <brutes> to any port ssh
    pass in on $outside_if proto tcp from any to $outside_if \
        port ssh flags S/SA keep state \
        (max-src-conn 10, max-src-conn-rate 5/60, \
        overload <brutes> flush global)
From at 2006-02-08 00:01:56:

The simplest way, in my opinion, to avoid pesky ssh worms is to put your sshd on a non-standard port. Sure, somebody could find it if they wanted to, but worms aren't that persistent. They try the standard port and move on. People say "security through obscurity" is no security at all, but the majority of "attacks" launched against systems today are not coming from determind hackers, they're coming from mindless scripts. And openssh makes it easy to use non-standard ports, because you can create a ~/.ssh/config file to specify the port (and other parameters) for your favorite hosts:

Host gorp

       Port             31337
From at 2006-06-24 19:25:59:

Well, I'd just like to say this is a brilliant way to handle all kinds of repetitive attacks. It's obviously easily adapted to any other port, and the simplicity of this technique inpires confidence.


From at 2008-05-12 13:11:51:

I have been using this technique for some time (Thanks Chris), but it looks like its time has passed. I am now seeing only a single attempt to log in from hundreds of hosts, in a clearly coordinated attempt. User benjamin from, then benjy from, then bennett from, then benny from .....

Written on 01 December 2005.
« An advantage to introspection and an interactive interpreter
Iptables modules that aren't in the iptables manpage »

Page tools: View Source, View Normal, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Thu Dec 1 03:19:40 2005
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.