randombio.com | commentary Saturday, May 13, 2017 Blocking sshd brute force attacks in LinuxHow to configure two commonly used methods |
If you run sshd, you already know why you need to block brute force attacks. They're not a security threat as long as you have a good password policy, but they clog up your logs with junk, which makes it hard to track down a real threat. With these techniques, the attacker only gets a fixed number of tries before they're permanently locked out.
There are two commonly used ways to do this.
Advantages: It's transparent, easy to extend to other services, and it's easy to block or unblock sites manually. Until recently, this was the most widely used method.
Disadvantages: It requires the libwrap library and a specially compiled version
of sshd. Openssh dropped libwrap support after version 6.6. Versions later than this
cannot use hosts.deny
files. TCP wrappers can be found
here.
Linux distros used to come with sshd pre-compiled with TCP Wrappers. Nowadays you have to build it yourself. This section covers TCP Wrappers 7.6 and OpenSSH 6.0.
To compile TCP Wrappers, make all the files in the tcp wrappers directory
writable (chmod a+rw *
) and edit the top of percent_m.c to get rid
of the redefined sys_errlist declaration:
#ifndef SYS_ERRLIST_DEFINED
/*
extern char *sys_errlist[];
extern int sys_nerr;
*/
#endif
then comment out the malloc line at the top of scaffold.c like so:
/*extern char *malloc();*/
This fixes the duplicate definition error.
Next edit the Makefile and uncomment the line
#REAL_DAEMON_DIR=/usr/sbin
to this
REAL_DAEMON_DIR=/usr/sbin
On my system it was also necessary to add -DHOSTS_ACCESS
to the
EXTRA_CFLAGS
line, making the linux section look like this:
linux:
@make REAL_DAEMON_DIR=$(REAL_DAEMON_DIR) STYLE=$(STYLE) \
LIBS= RANLIB=ranlib ARFLAGS=rv AUX_OBJ=setenv.o \
NETGROUP= TLI= EXTRA_CFLAGS="-DBROKEN_SO_LINGER -DHOSTS_ACCESS" all
If this was skipped, it would compile but ssh still couldn't use the hosts.deny files.
Then type make linux
and manually install libwrap.a
and
tcpd.h
somewhere (e.g. in /lib64
and /usr/include
).
To test if your version of sshd has tcpwrappers (libwrap) installed, type
strings /usr/sbin/sshd | grep wrap
If libwrap support is enabled, it will say:
Connection refused by tcp wrapper
Notice you can't use ldd /usr/sbin/sshd, because it could have been compiled with the static libwrap.a library, so nothing would be dynamically linked in for ldd to see.
The latest version of openssh that supports tcpwrappers is 6.6, so compile openssh-6.6p1 using the command
configure --with-tcp-wrappers
Note that the tcpwrappers part (above) has to be done first, or you will get the
message configure: error: *** libwrap missing
Then type make. It will say something like:
/usr/lib64/gcc/x86_64-suse-linux/4.7/../../../../x86_64-suse-linux/bin/ld:
/lib/../lib64/libwrap.a(hosts_access.o): relocation R_X86_64_32 against
`.data' can not be used when making a shared object; recompile with -fPIC
/lib/../lib64/libwrap.a: could not read symbols: Bad value
collect2: error: ld returned 1 exit status
fPIC, of course, means either Florida Professionals in Infection Control or
Position Independent Code. If you get this error, do what it says: add -fPIC
to the Makefile for tcpwrappers and recompile and reinstall tcpwrappers.
If it compiles, type make install
.
A more modern way to block sshd brute force attacks is to use a little brute force
of your own. That means iptables
.
This
site tells you why. It's a bit trickier to get it right, and so far I haven't found
the perfect set of rules. Here's a quick refresher of some iptables commands.
Command | Function |
---|---|
iptables -L | list rules |
iptables -F | flush, erases all the rules |
iptables -X | delete an empty iptables chain |
iptables -I | insert rules |
Here's one ruleset that seems to be widely used. It simply drops any new connections that come in faster than four times a minute:
/usr/sbin/iptables -I INPUT -p tcp --dport 22 -i eth0 -m state --state NEW -m recent --set
/usr/sbin/iptables -I INPUT -p tcp --dport 22 -i eth0 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
If add these rules, you may get the error message:
WARNING: The state match is obsolete. Use conntrack instead.
This is your computer trying to hint that constantly reinventing the wheel by changing the interface of software is a bad thing. It still accepts the ‘obsolete’ rule, though, and if you list the rules it shows you what the computer thinks you meant:
Chain INPUT (policy ACCEPT)
target prot opt source destination
DROP tcp -- anywhere anywhere tcp dpt:ssh ctstate NEW recent: UPDATE seconds: 60 hit_count: 4 name: DEFAULT side: source mask: 255.255.255.255
tcp -- anywhere anywhere tcp dpt:ssh ctstate NEW recent: SET name: DEFAULT side: source mask: 255.255.255.255
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Here' a set of rules that uses conntrack (from here). (Note: I haven't tested them).
iptables -I INPUT 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 ! -s 192.168.1.0/24 -m conntrack --ctstate NEW -m recent --set --name SSH
iptables -A INPUT -p tcp --dport 22 ! -s 192.168.1.0/24 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 2 --rttl --name SSH -j DROP
iptables -A INPUT -p tcp --dport 22 ! -s 192.168.1.0/24 -m conntrack --ctstate NEW -m recent --update --seconds 600 --hitcount 4 --rttl --name SSH -j DROP
This rule is supposed to drop the connection if the are more than 2 new login
attempts in 60 seconds drop the connection, or if there are 5 attempts in 600
seconds. The 192.168.1.0/24
is the IP range of your local network.