Preventing SSH Dictionary Attacks With DenyHosts

Version 1.0
Author: Falko Timme
Last edited: 02/07/2006

In this HowTo I will show how to install and configure DenyHosts. DenyHosts is a tool that observes login attempts to SSH, and if it finds failed login attempts again and again from the same IP address, DenyHosts blocks further login attempts from that IP address by putting it into /etc/hosts.deny. DenyHosts can be run by cron or as a daemon. In this tutorial I will run DenyHosts as a daemon.

From the DenyHosts web site:

"DenyHosts is a script intended to be run by Linux system administrators to help thwart ssh server attacks.

If you've ever looked at your ssh log (/var/log/secure on Redhat, /var/log/auth.log on Mandrake, etc...) you may be alarmed to see how many hackers attempted to gain access to your server. Hopefully, none of them were successful (but then again, how would you know?). Wouldn't it be better to automatically prevent that attacker from continuing to gain entry into your system?

DenyHosts attempts to address the above... "

This tutorial is based on a Debian Sarge system, however, it should apply to other distributions with almost no modifications.

I want to say first that this is not the only way of setting up such a system. There are many ways of achieving this goal but this is the way I take. I do not issue any guarantee that this will work for you!

1 Installation

DenyHosts is written in Python, therefore we must install Python and also the Python development files first:

apt-get install python python2.3-dev python2.3

Then we download and install DenyHosts like this:

cd /tmp
wget http://mesh.dl.sourceforge.net/sourceforge/denyhosts/DenyHosts-2.0.tar.gz
tar xvfz DenyHosts-2.0.tar.gz
cd DenyHosts-2.0
python setup.py install

This installs DenyHosts to /usr/share/denyhosts.

2 Configuration

Now we have to create the DenyHosts configuration file /usr/share/denyhosts/denyhosts.cfg. We can use the sample configuration file /usr/share/denyhosts/denyhosts.cfg-dist for this:

cd /usr/share/denyhosts
cp denyhosts.cfg-dist denyhosts.cfg

Then we must edit denyhosts.cfg with our favourite editor such as vi, for example. Mine looks like this:

       ############ THESE SETTINGS ARE REQUIRED ############

########################################################################
#
# SECURE_LOG: the log file that contains sshd logging info
# if you are not sure, grep "sshd:" /var/log/*
#
# The file to process can be overridden with the --file command line
# argument
#
# Redhat or Fedora Core:
#SECURE_LOG = /var/log/secure
#
# Mandrake, FreeBSD or OpenBSD:
SECURE_LOG = /var/log/auth.log
#
# SuSE:
#SECURE_LOG = /var/log/messages
#
########################################################################

########################################################################
# HOSTS_DENY: the file which contains restricted host access information
#
# Most operating systems:
HOSTS_DENY = /etc/hosts.deny
#
# Some BSD (FreeBSD) Unixes:
#HOSTS_DENY = /etc/hosts.allow
#
# Another possibility (also see the next option):
#HOSTS_DENY = /etc/hosts.evil
#######################################################################


########################################################################
# PURGE_DENY: removed HOSTS_DENY entries that are older than this time
# when DenyHosts is invoked with the --purge flag
#
# format is: i[dhwmy]
# Where 'i' is an integer (eg. 7)
# 'm' = minutes
# 'h' = hours
# 'd' = days
# 'w' = weeks
# 'y' = years
#
# never purge:
PURGE_DENY =
#
# purge entries older than 1 week
#PURGE_DENY = 1w
#
# purge entries older than 5 days
#PURGE_DENY = 5d
#######################################################################


#######################################################################
# BLOCK_SERVICE: the service name that should be blocked in HOSTS_DENY
#
# man 5 hosts_access for details
#
# eg. sshd: 127.0.0.1 # will block sshd logins from 127.0.0.1
#
# To block all services for the offending host:
#BLOCK_SERVICE = ALL
# To block only sshd:
BLOCK_SERVICE = sshd
# To only record the offending host and nothing else (if using
# an auxilary file to list the hosts). Refer to:
# http://denyhosts.sourceforge.net/faq.html#aux
#BLOCK_SERVICE =
#
#######################################################################


#######################################################################
#
# DENY_THRESHOLD_INVALID: block each host after the number of failed login
# attempts has exceeded this value. This value applies to invalid
# user login attempts (eg. non-existent user accounts)
#
DENY_THRESHOLD_INVALID = 5
#
#######################################################################

#######################################################################
#
# DENY_THRESHOLD_VALID: block each host after the number of failed
# login attempts has exceeded this value. This value applies to valid
# user login attempts (eg. user accounts that exist in /etc/passwd) except
# for the "root" user
#
DENY_THRESHOLD_VALID = 10
#
#######################################################################

#######################################################################
#
# DENY_THRESHOLD_ROOT: block each host after the number of failed
# login attempts has exceeded this value. This value applies to
# "root" user login attempts only.
#
DENY_THRESHOLD_ROOT = 5
#
#######################################################################


#######################################################################
#
# WORK_DIR: the path that DenyHosts will use for writing data to
# (it will be created if it does not already exist).
#
# Note: it is recommended that you use an absolute pathname
# for this value (eg. /home/foo/denyhosts/data)
#
WORK_DIR = /usr/share/denyhosts/data
#
#######################################################################

#######################################################################
#
# SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS
#
# SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS=YES|NO
# If set to YES, if a suspicious login attempt results from an allowed-host
# then it is considered suspicious. If this is NO, then suspicious logins
# from allowed-hosts will not be reported. All suspicious logins from
# ip addresses that are not in allowed-hosts will always be reported.
#
SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS=YES
######################################################################

######################################################################
#
# HOSTNAME_LOOKUP
#
# HOSTNAME_LOOKUP=YES|NO
# If set to YES, for each IP address that is reported by Denyhosts,
# the corresponding hostname will be looked up and reported as well
# (if available).
#
HOSTNAME_LOOKUP=YES
#
######################################################################


######################################################################
#
# LOCK_FILE
#
# LOCK_FILE=/path/denyhosts
# If this file exists when DenyHosts is run, then DenyHosts will exit
# immediately. Otherwise, this file will be created upon invocation
# and deleted upon exit. This ensures that only one instance is
# running at a time.
#
# Redhat/Fedora:
#LOCK_FILE = /var/lock/subsys/denyhosts
#
# Debian
LOCK_FILE = /var/run/denyhosts.pid
#
# Misc
#LOCK_FILE = /tmp/denyhosts.lock
#
######################################################################


############ THESE SETTINGS ARE OPTIONAL ############


#######################################################################
#
# ADMIN_EMAIL: if you would like to receive emails regarding newly
# restricted hosts and suspicious logins, set this address to
# match your email address. If you do not want to receive these reports
# leave this field blank (or run with the --noemail option)
#
ADMIN_EMAIL =
#
#######################################################################

#######################################################################
#
SMTP_HOST = localhost
SMTP_PORT = 25
SMTP_FROM = DenyHosts <[email protected]>
SMTP_SUBJECT = DenyHosts Report
#SMTP_USERNAME=foo
#SMTP_PASSWORD=bar
#
#######################################################################

######################################################################
#
# ALLOWED_HOSTS_HOSTNAME_LOOKUP
#
# ALLOWED_HOSTS_HOSTNAME_LOOKUP=YES|NO
# If set to YES, for each entry in the WORK_DIR/allowed-hosts file,
# the hostname will be looked up. If your versions of tcp_wrappers
# and sshd sometimes log hostnames in addition to ip addresses
# then you may wish to specify this option.
#
#ALLOWED_HOSTS_HOSTNAME_LOOKUP=NO
#
######################################################################

######################################################################
#
# AGE_RESET_VALID: Specifies the period of time between failed login
# attempts that, when exceeded will result in the failed count for
# this host to be reset to 0. This value applies to login attempts
# to all valid users (those within /etc/passwd) with the
# exception of root. If not defined, this count will never
# be reset.
#
# See the comments in the PURGE_DENY section (above)
# for details on specifying this value or for complete details
# refer to: http://denyhosts.sourceforge.net/faq.html#timespec
#
AGE_RESET_VALID=5d
#
######################################################################

######################################################################
#
# AGE_RESET_ROOT: Specifies the period of time between failed login
# attempts that, when exceeded will result in the failed count for
# this host to be reset to 0. This value applies to all login
# attempts to the "root" user account. If not defined,
# this count will never be reset.
#
# See the comments in the PURGE_DENY section (above)
# for details on specifying this value or for complete details
# refer to: http://denyhosts.sourceforge.net/faq.html#timespec
#
AGE_RESET_ROOT=25d
#
######################################################################

######################################################################
#
# AGE_RESET_INVALID: Specifies the period of time between failed login
# attempts that, when exceeded will result in the failed count for
# this host to be reset to 0. This value applies to login attempts
# made to any invalid username (those that do not appear
# in /etc/passwd). If not defined, count will never be reset.
#
# See the comments in the PURGE_DENY section (above)
# for details on specifying this value or for complete details
# refer to: http://denyhosts.sourceforge.net/faq.html#timespec
#
AGE_RESET_INVALID=10d
#
######################################################################

######################################################################
#
# PLUGIN_DENY: If set, this value should point to an executable
# program that will be invoked when a host is added to the
# HOSTS_DENY file. This executable will be passed the host
# that will be added as it's only argument.
#
#PLUGIN_DENY=/usr/bin/true
#
######################################################################


######################################################################
#
# PLUGIN_PURGE: If set, this value should point to an executable
# program that will be invoked when a host is removed from the
# HOSTS_DENY file. This executable will be passed the host
# that is to be purged as it's only argument.
#
#PLUGIN_PURGE=/usr/bin/true
#
######################################################################

######################################################################
#
# USERDEF_FAILED_ENTRY_REGEX: if set, this value should contain
# a regular expression that can be used to identify additional
# hackers for your particular ssh configuration. This functionality
# extends the built-in regular expressions that DenyHosts uses.
# This parameter can be specified multiple times.
# See this faq entry for more details:
# http://denyhosts.sf.net/faq.html#userdef_regex
#
#USERDEF_FAILED_ENTRY_REGEX=
#
#
######################################################################




######### THESE SETTINGS ARE SPECIFIC TO DAEMON MODE ##########



#######################################################################
#
# DAEMON_LOG: when DenyHosts is run in daemon mode (--daemon flag)
# this is the logfile that DenyHosts uses to report it's status.
# To disable logging, leave blank. (default is: /var/log/denyhosts)
#
DAEMON_LOG = /var/log/denyhosts
#
# disable logging:
#DAEMON_LOG =
#
######################################################################

#######################################################################
#
# DAEMON_LOG_TIME_FORMAT: when DenyHosts is run in daemon mode
# (--daemon flag) this specifies the timestamp format of
# the DAEMON_LOG messages (default is the ISO8061 format:
# ie. 2005-07-22 10:38:01,745)
#
# for possible values for this parameter refer to: man strftime
#
# Jan 1 13:05:59
#DAEMON_LOG_TIME_FORMAT = %b %d %H:%M:%S
#
# Jan 1 01:05:59
#DAEMON_LOG_TIME_FORMAT = %b %d %I:%M:%S
#
######################################################################

#######################################################################
#
# DAEMON_LOG_MESSAGE_FORMAT: when DenyHosts is run in daemon mode
# (--daemon flag) this specifies the message format of each logged
# entry. By default the following format is used:
#
# %(asctime)s - %(name)-12s: %(levelname)-8s %(message)s
#
# Where the "%(asctime)s" portion is expanded to the format
# defined by DAEMON_LOG_TIME_FORMAT
#
# This string is passed to python's logging.Formatter contstuctor.
# For details on the possible format types please refer to:
# http://docs.python.org/lib/node357.html
#
# This is the default:
#DAEMON_LOG_MESSAGE_FORMAT = %(asctime)s - %(name)-12s: %(levelname)-8s %(message)s
#
#
######################################################################


#######################################################################
#
# DAEMON_SLEEP: when DenyHosts is run in daemon mode (--daemon flag)
# this is the amount of time DenyHosts will sleep between polling
# the SECURE_LOG. See the comments in the PURGE_DENY section (above)
# for details on specifying this value or for complete details
# refer to: http://denyhosts.sourceforge.net/faq.html#timespec
#
#
DAEMON_SLEEP = 30s
#
#######################################################################

#######################################################################
#
# DAEMON_PURGE: How often should DenyHosts, when run in daemon mode,
# run the purge mechanism to expire old entries in HOSTS_DENY
# This has no effect if PURGE_DENY is blank.
#
DAEMON_PURGE = 1h
#
#######################################################################


######### THESE SETTINGS ARE SPECIFIC TO ##########
######### DAEMON SYNCHRONIZATION ##########


#######################################################################
#
# Synchronization mode allows the DenyHosts daemon the ability
# to periodically send and receive denied host data such that
# DenyHosts daemons worldwide can automatically inform one
# another regarding banned hosts. This mode is disabled by
# default, you must uncomment SYNC_SERVER to enable this mode.
#
# for more information, please refer to:
# http:/denyhosts.sourceforge.net/faq.html#sync
#
#######################################################################


#######################################################################
#
# SYNC_SERVER: The central server that communicates with DenyHost
# daemons. Currently, denyhosts.net is the only available server
# however, in the future, it may be possible for organizations to
# install their own server for internal network synchronization
#
# To disable synchronization (the default), do nothing.
#
# To enable synchronization, you must uncomment the following line:
#SYNC_SERVER = http://xmlrpc.denyhosts.net:9911
#
#######################################################################

#######################################################################
#
# SYNC_INTERVAL: the interval of time to perform synchronizations if
# SYNC_SERVER has been uncommented. The default is 1 hour.
#
#SYNC_INTERVAL = 1h
#
#######################################################################


#######################################################################
#
# SYNC_UPLOAD: allow your DenyHosts daemon to transmit hosts that have
# been denied? This option only applies if SYNC_SERVER has
# been uncommented.
#
#SYNC_UPLOAD = no
#
# the default:
#SYNC_UPLOAD = yes
#
#######################################################################


#######################################################################
#
# SYNC_DOWNLOAD: allow your DenyHosts daemon to receive hosts that have
# been denied by others? This option only applies if SYNC_SERVER has
# been uncommented.
#
#SYNC_DOWNLOAD = no
#
# the default:
#SYNC_DOWNLOAD = yes
#
#######################################################################

#######################################################################
#
# SYNC_DOWNLOAD_THRESHOLD: If SYNC_DOWNLOAD is enabled this paramter
# filters the returned hosts to those that have been blocked this many
# times by others. That is, if set to 1, then if a single DenyHosts
# server has denied an ip address then you will receive the denied host.
#
#SYNC_DOWNLOAD_THRESHOLD = 10
#
# the default:
#SYNC_DOWNLOAD_THRESHOLD = 3
#
#######################################################################

Make sure you set SECURE_LOG and LOCK_FILE to the correct values for your distribution! For Debian, these are:

SECURE_LOG = /var/log/auth.log
LOCK_FILE = /var/run/denyhosts.pid

As we want to run DenyHosts as a daemon, we need the daemon control script /usr/share/denyhosts/daemon-control. Again, we can use the sample script /usr/share/denyhosts/daemon-control-dist to create the needed file:

cp daemon-control-dist daemon-control

Edit /usr/share/denyhosts/daemon-control and make sure you set the correct values for DENYHOSTS_BIN, DENYHOSTS_LOCK, and DENYHOSTS_CFG. For Debian, these are:

DENYHOSTS_BIN = "/usr/bin/denyhosts.py"
DENYHOSTS_LOCK = "/var/run/denyhosts.pid"
DENYHOSTS_CFG = "/usr/share/denyhosts/denyhosts.cfg"

So my /usr/share/denyhosts/daemon-control file looks like this:

#!/usr/bin/env python
# denyhosts Bring up/down the DenyHosts daemon
#
# chkconfig: 2345 98 02
# description: Activates/Deactivates the
# DenyHosts daemon to block ssh attempts
#
###############################################

###############################################
#### Edit these to suit your configuration ####
###############################################

DENYHOSTS_BIN = "/usr/bin/denyhosts.py"
DENYHOSTS_LOCK = "/var/run/denyhosts.pid"
DENYHOSTS_CFG = "/usr/share/denyhosts/denyhosts.cfg"


###############################################
#### Do not edit below ####
###############################################

import os, sys, signal, time

STATE_NOT_RUNNING = -1
STATE_LOCK_EXISTS = -2

def usage():
print "Usage: %s {start [args...] | stop | restart [args...] | status | debug | condrestart [args...] }" % sys.argv[0]
print
print "For a list of valid 'args' refer to:"
print "$ denyhosts.py --help"
print
sys.exit(0)


def getpid():
try:
fp = open(DENYHOSTS_LOCK, "r")
pid = int(fp.readline().rstrip())
fp.close()
except Exception, e:
return STATE_NOT_RUNNING

if os.access(os.path.join("/proc", str(pid)), os.F_OK):
return pid
else:
return STATE_LOCK_EXISTS


def start(*args):
cmd = "%s --daemon " % DENYHOSTS_BIN
if args: cmd += ' '.join(args)

print "starting DenyHosts: ", cmd

os.system(cmd)


def stop():
pid = getpid()
if pid >= 0:
os.kill(pid, signal.SIGTERM)
print "sent DenyHosts SIGTERM"
else:
print "DenyHosts is not running"

def debug():
pid = getpid()
if pid >= 0:
os.kill(pid, signal.SIGUSR1)
print "sent DenyHosts SIGUSR1"
else:
print "DenyHosts is not running"

def status():
pid = getpid()
if pid == STATE_LOCK_EXISTS:
print "%s exists but DenyHosts is not running" % DENYHOSTS_LOCK
elif pid == STATE_NOT_RUNNING:
print "Denyhosts is not running"
else:
print "DenyHosts is running with pid = %d" % pid


def condrestart(*args):
pid = getpid()
if pid >= 0:
restart(*args)


def restart(*args):
stop()
time.sleep(1)
start(*args)


if __name__ == '__main__':
cases = {'start': start,
'stop': stop,
'debug': debug,
'status': status,
'condrestart': condrestart,
'restart': restart}

try:
args = sys.argv[2:]
except:
args = []

try:
option = sys.argv[1]

if option in ('start', 'restart', 'condrestart'):
if '--config' not in args and '-c' not in args:
args.append("--config=%s" % DENYHOSTS_CFG)

cmd = cases[option]
apply(cmd, args)
except:
usage()

Next we have to make that file executable:

chown root daemon-control
chmod 700 daemon-control

Afterwards, we create the system bootup links for DenyHosts do that it is started automatically when the system is booted:

cd /etc/init.d
ln -s /usr/share/denyhosts/daemon-control denyhosts
update-rc.d denyhosts defaults

Finally, we start DenyHosts:

/etc/init.d/denyhosts start

DenyHosts logs to /var/log/denyhosts, if you are interested in the logs. The SSH daemon logs to /var/log/auth.log on Debian. You can watch both logs and try to log in with an invalid user or with a valid user and incorrect password, etc. via SSH and see what happens. After you have crossed the threshold of incorrect login attempts, the IP address from which you tried to connect should get listed in /etc/hosts.deny, like this:

# /etc/hosts.deny: list of hosts that are _not_ allowed to access the system.
# See the manual pages hosts_access(5), hosts_options(5)
# and /usr/doc/netbase/portmapper.txt.gz
#
# Example: ALL: some.host.name, .some.domain
# ALL EXCEPT in.fingerd: other.host.name, .other.domain
#
# If you're going to protect the portmapper use the name "portmap" for the
# daemon name. Remember that you can only use the keyword "ALL" and IP
# addresses (NOT host or domain names) for the portmapper. See portmap(8)
# and /usr/doc/portmap/portmapper.txt.gz for further information.
#
# The PARANOID wildcard matches any host whose name does not match its
# address.

# You may wish to enable this to ensure any programs that don't
# validate looked up hostnames still leave understandable logs. In past
# versions of Debian this has been the default.
# ALL: PARANOID
sshd: 192.168.0.203

This means that the system with the IP address 192.168.0.203 cannot connect anymore using SSH.

You can specify if/when IP addresses are removed again from /etc/hosts.deny - have a look at the PURGE_DENY variable in /usr/share/denyhosts/denyhosts.cfg. You must start DenyHosts with the --purge option to make the PURGE_DENY variable effective, like this:

/etc/init.d/denyhosts start --purge

However, you can also remove IP addresses manually from there, and as soon as they have got removed, these IP addresses can try to log in again via SSH.

Links

Share this page:

43 Comment(s)

Add comment

Please register in our forum first to comment.

Comments

By: Anonymous

Denyhosts is alredy packaged for debian distribution and it run out of the box. Officially is part of SID branch, and unofficially, I'm maintaing last version in my repository:

http://bertorello.ns0.it/debian/binary/denyhosts/


Note: init script in debian package are full LSB-compilant. The one in author's tarball is a python script.

By: admin

I've tried the Debian Package on Debian Sarge. First, you must have the package lsb-base installed, because the init script needs it, and furthermore the init script needs the function log_daemon_msg which isn't included in the lsb funtions. Therefore the init script fails. You can make it work by creating the function log_daemon_msg yourself, but it's not the clean way.

If you could adjust your Debian packages so that they work on Debian Sarge it would be great! :-)

By: Anonymous

There is little use (IMHO) to complain about sid packages that do not work on sarge. You cannot expect the packager to handle those too, as Sarge has been released already so no new packages will go into it.

By: Anonymous

I used the .deb package on Ubuntu 5.10 and ran into the same problem with log_daemon_msg. For me the easy way out was changing the init-script's use of this function to log_success_msg which is in the lsb.

By: Anonymous

Thanks for the heads up on this people, however the site listed above (for debian package) isn't working..

Easy enough to build and install though...

By: Anonymous

pam_abl is a PAM component that auto blacklists hosts responsible for repeated failed login attempts. http://hexten.net/pam_abl

By: Anonymous

I was just wondering if there wasn't such a PAM module... looks to me that PAM is the aproach that makes more sense.

By: Anonymous

Nice one - this helps a lot.

By:
By: Anonymous

Falko,

The --purge command is only necessary when running from the command line. If you run DenyHosts as a daemon and set your PURGE_INTERVAL, then DenyHosts will automatically purge the expired entries that are older than PURGE_DENY every PURGE_INTERVAL. That is, if PURGE_DENY = 5d (5 days) and PURGE_INTERVAL = 1h (1 hour) then each hour DenyHosts will remove any /etc/hosts.deny entries that are older than 5 days.

Other than that, great article!

Readers may also be interested in the new synchronization mode too. For more information: http://www.denyhosts.net/faq.html#sync

Phil Schwartz

(you might also remember me from such projects as DenyHosts)

By: Anonymous

Hi, I discovered the DenyHostscript sometime ago. I think it was through /. It has now been running for a few months on my server and manages in general to repel around two attacks a day.

By: Anonymous

# apt-get install fail2ban

By: Anonymous

One of the benefits of running Debian Sarge is the ability to run servers without too much change, and also the ability to apt-get packages. This allows one to run servers and other services without worrying about and manually tracking security updates. Simply cronning apt-get update/upgrade nightly, plus manually checking for packages that fail --assume-yes (trivial) regularly allows one to keep a system up to date for patches and for security issues.

One issue when Sarge was still in testing was when KDE went through a major upgrade. kdm/gdm then required a large number of manual changes to retain the changes made to the original kdm/gdm when previously installed.

There are two concerns with this example and not using non-deb packages. The first is the changes that would need to be made to the config file after an update/upgrade to the next version. The second concern is, wouldn't the config file be overwritten, or wouldn't manual intervention be needed due to the config file being located outside of /etc? This is especially a concern when dealing with packages that have to do with the security of the system. What if I forget that the package has a config file outside of /etc, update/upgrade it or install the latest tarball, then am left with an overwritten config file? Or realize what happened, but didn't schedule time to re-configure the config file?

While the alternative is easy to understand, no package (for Sarge) if relying on a deb file due to the issues brought up in the comments, or package/functionality available if using the tarball, as a non-guru, I'm relying on these great how-to's to kickstart my venture into other services. As I believe quite a few others are. Wouldn't it be a good idea to give a heads up about the possibility of overwriting a config file during an upgrade of the package?

Great how-to anyway. As are the others. Keep them coming!

By: Anonymous

Another approach that is more generic (can be used with any port/service) is to use the IPT_RECENT module that comes with netfilter:

For example I have the following lines in my iptables config:

iptables -N SSH_CHECK
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j SSH_CHECK
iptables -A SSH_CHECK -m state --state NEW -m recent --set --name SSH
iptables -A SSH_CHECK -m state --state NEW -m recent --update --seconds 60 --hitcount 4 --name SSH
iptables -A SSH_CHECK -m state --state NEW -m recent --rcheck --seconds 60 --hitcount 4 --name SSH -j DROP

which basically kick-bans the source IP for 60 seconds if more than 3 connections are attempted in a 60 second limit.

I've found this to be 100% effective.

By: Anonymous

This will watch and if there are more than 3 ssh connections within a 60
second span, new ssh connections will be rate limited. Existing connections
are unaffected, and anything in your white list is unaffected. You can still
make new SSH connections, though it takes a lot longer for the session to come
up.

iptables -N ALLOWED
# accept already establed and related connections
iptables -A INPUT -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
# don't rate limit ssh from the following
iptables -A INPUT -s $WHITE_LIST_IP_ADDRESS -p tcp -m tcp --dport 22 -j ACCEPT
# rate limit on excessive ssh connections
iptables -A INPUT -i eth0 -p tcp -m tcp --dport 22 -j ALLOWED
# rate limiting
iptables -A ALLOWED -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A ALLOWED -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -m limit --limit 3/min --limit-burst 3 -j ACCEPT
iptables -A ALLOWED -p tcp -j LOG --log-tcp-options --log-ip-options --log-prefix " DROP RATE_LIMIT "
iptables -A ALLOWED -p tcp -j REJECT --reject-with icmp-port-unreachable


Obviously you would need to integrate this into your existing iptables config.
I personally use iptables-save / iptables-restore to keep and edit my
configuration.

By: Anonymous

And this iptables solution is far much better than anything else I've found yet. I can't predict who and from where the attack is. It could be a windows machine on a dial-up. So permanent blacklisting isn't an option. iptables allows me to block them temporarily and that's much better. :-)

By: Anonymous

I have successfully used "Another approach" on several of my systems. But i have one 4-processor AMD system running a 64-bit kernel.

Apparently, on that specific system with kernels around 2.6.8 (say a Debian stock kernel) or so, this recipe causes a crash after about 25 days.

It may be fixed around 2.6.12, so with a newer kernel you'd probably be in ok shape.

By: Anonymous

i like the iptables solution pretty well (it's stock), but would like to log failed attempts... any suggestions please post here and also email to [email protected]


thanks!

pau1

By: Anonymous

before the drop line you could do this: iptables -A SSH_CHECK -m recent --update --seconds 60 --hitcount 4 --name SSH -j LOG --log-prefix "SSH Flood: " --log-level info -xaeth

By: Anonymous

Pau1,

If you want log and view failed attempts, install Logcheck. I use it under Debian stable and it will email you every couple hours of any major changes in your system or failed access attempts to your machine. All I did to fix the ssh hacker attempts was to change the port number that ssh listens on. Now I really don't have a hacker attempt problem. Hope this helps.

By: Anonymous

Hello,

I'm also using the recent match in iptables, and according to me , your setup can be simplified :

iptables -A SSH_CHECK -m recent --update --seconds 60 --hitcount 4 --name SSH

in stead of the last two in your example.

I've left out the -m state --state NEW because it's allway true, and

the rule with the --update option can be left out, by just replacing the --rcheck in the last rule by --update.

It becomes then:

iptables -N SSH_CHECK

# here come checks for integrity, portscans(psd) etc

# here allow related traffic

iptables -A INPUT -p tcp --dport 22 -m state NEW -j SSH_CHECK

iptables -A SSH_CHECK -m recent --set --name SSH

iptables -A SSH_CHECK -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP

Stef

By: Anonymous

make that

...  -m state --state NEW ...

I found this post very helpful to get me started with Linux firewalls. I'm just a bit surprised that nobody mentions ip6tables - as I understand it that door will remain wide wide open! And of course ip6tables does not support "recent" so we better unconditionally drop there.

 Dirk

By:

Personally i think the iptables way is the most optimal way to go, even you understand the OSI layers you will agree with me, with iptables the packet will be dropped at layer 4, but with denyhosts it will traverse the path up to layer 7 before a decision can be made.

By: Anonymous

Why is your system open to everyone? Wouldn't "default deny" and granting

access to people who should be using it be a much safer alternative?

By: Anonymous

Even allowing only a few users to get access does not prevent Dictionary attacks on the accounts. If you host a shell server for example, all users need SSH access and DenyHosts is a good system to prevent dictionary attacks.

By: Anonymous

If you want to try an alternative method of blocking dictionary attacks on SSH, try sshdfilter: http://www.csc.liv.ac.uk/~greg/sshdfilter/ It runs as a daemon, like Denyhosts. It executes SSHD itself, and so blocks can be initiated instantly (the program is constantly monitoring your sshd log). It also logs all attempts, and a supplied Logwatch script can give you periodical summaries.

By:

Been using sshdfilter for quite a while on one of my server and i can say i love it. I had trouble in 2004 with some dict. attacks, but in 2006 i tried sshd and it worked quite well. I heard DenyHost is pretty good too, haven't tried it yet though. The recipe for success might be to get ready before the attacks instead of patching after :)

Patt's Frostings

By: Anonymous

First of all, thanks for the great write-up. denyhosts immediately blocked 14 hosts upon startup!

Secondly, for redhat/fedora, you can subsitute the following command in the tutorial:

update-rc.d denyhosts defaults
with the following:
chkconfig denyhosts --add

to accomplish the same end. -tm

By: Anonymous

If python complains about a missing Makefile when you run "python setup.py install", you might be missing the python-dev package -- I was. "apt-get install python-dev" and I was good to go.

By: Anonymous

1. AllowGroup - in sshd_config will allow only users in specific groups. Our servers have alot more users than just those who need ssh access.

2. A little ugly perl script which will email a netblock admin where the attack came from ONCE PER ATTEMPT that a host on their network has been compromised:
http://www.neuropunks.org/~max/parse_ssh.pl

This is really only usefull on FreeBSD machines which email root a security summary for the day. In our case, it is called from procmail to parse that message.

Heres a recipe:

:0c
* ^Subject:.*security*
| /root/system/bin/parse_ssh.pl

By: Anonymous

Well, this might be of intrest to all off you, i thougt to share it here ...

From the website: http://denyhosts.sourceforge.net/

Denyhosts v2.3 contains a security fix

(purged hosts were not always re-added when they should have been). If you are using an earlier version it is strongly recommended that you upgrade to v2.3 or later

PS the latest version is 2.4b April 9, 2006

Gr Ovis

By:

First, I want to thank Falko Timme for the helpful article on setting up DenyHosts
for Debian. It was very clear and easy to follow. I got DenyHosts running in
no time.

After I got it working, I couldn't help but move things around to make it
more "standards-compliant". I didn't want to use the .deb package because it's still in testing and i'm using stable. So Here's how i went about it:

mkdir /var/lib/denyhosts
chown root:root /var/lib/denyhosts
chmod 750 /var/lib/denyhosts

Then modified /usr/share/denyhosts.cfg to include:
WORK_DIR = /var/lib/denyhosts

Run it once (to create the files inside $WORK_DIR) then:
chmod o-rwx -R /var/lib/denyhosts

mkdir /etc/denyhosts
chown root:root /etc/denyhosts
chmod 750 /etc/denyhosts
mv /usr/share/denyhosts/daemon-control /etc/denyhosts/
mv /usr/share/denyhosts/denyhosts.cfg /etc/denyhosts/
chmod o-rwx -R /etc/denyhosts

Also:
rm /etc/init.d/denyhosts
ln -s /etc/denyhosts/daemon-control /etc/init.d/denyhosts

and modify /etc/denyhosts/daemon-control to include:
DENYHOSTS_CFG = "/etc/denyhosts/denyhosts.cfg"

And finally:
chmod o-rwx -R /usr/share/denyhosts
chmod o-rwx /usr/bin/denyhosts.py

PS.
As I wanted to always run it with the --purge option i simply edited
/etc/denyhosts/daemon-control to include:
DENYHOSTS_BIN = "/usr/bin/denyhosts.py --purge"
 
et voila

By:
By: Bruce Elaine

Hello,Please someone should please teach me how to find smtp using putty and also give me every code needed to get the smtp,here is my email,i will appreciate your prompt response,please send me every details on how to get smtp to [email protected]

By: Ganesh Waghumbare

Hi Falko,

    It's really helpful guide.

Thanks and Regards,

Ganesh Waghumbare.


By: Bmor

Falko, Your "how to" still works great for setting up DenyHosts-2.6 on Ubuntu Server 9.04 http://sourceforge.net/projects/denyhosts/files/denyhosts/2.6/DenyHosts-2.6.tar.gz/download There is ONE "path correction" however, in the daemon-control file change; DENYHOSTS_BIN = "/usr/bin/denyhosts.py" to DENYHOSTS_BIN = "/usr/local/bin/denyhosts.py" Thanks for your expertise....

By: oneyearuser

wow i was stoked to find this .... then i installed it and found out that i had to go back to college to learn how to get it configured with all these config files ..... there isn't a reason in the world that you couldnt have included the config files in the install ................ this right here is why linux will NEVER be a reasonable desktop replacement os .... less then 1% of computer users will spend 5 years studying to learn the code required to run a linux system the rest will rely on point and click uninstalling now to look for a security utility that wont take me a month to figure out

By: Anonymous

oneyearuser you should use a newer howto on denyhosts since it already seems to be included in repositories (with configfiles included in the install)

on the other hand, why would you even need denyhosts on a desktop-machine? where you assumably don't have a ssh-server installed. 



By: Jamie Kitson

Note that the above post is four years old. On my Debian system the config is in /etc/ and I think was setup automatically.

 Jamie

By:

I see my hosts.deny file has  grown to about 10000 entries after 12 months,

Is there a max file size for hosts.deny?

If over a few years it grows to many 10000's does this affect performance to the server eventually?

I currently  have  set  PURGE_DENY =

Should I change it to something like PURGE_DENY = 360d           ?????

Any thoughts appreciated

By: Anonymous

This is perfect for blocking all those china hacking attempts! Thanks for the How-To and thanks to the Denyhosts developer!

By: Isaiah Schisler

Great article Falko. 

 I know this article was written 6 years ago, but just thought I'd give you heads-up the download URL provided isn't working anymore.

 

This worked for me: wget "http://downloads.sourceforge.net/project/denyhosts/denyhosts/2.6/DenyHosts-2.6.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fdenyhosts%2Ffiles%2F&ts=1339534547&use_mirror=voxel" -O DenyHosts-2.6.tar.gz

By: Dave

Great tutorial! Thanks!

DENYHOSTS_BIN = "/usr/bin/denyhosts.py"

should be

DENYHOSTS_BIN = "/usr/local/bin/denyhosts.py"