Vermeidung von SSH Dictionary Attacks mit DenyHosts

Version 1.0 Author: Falko Timme Last edited: 02/07/2006
Diese Anleitung veranschaulicht, wie man DenyHosts installiert und konfiguriert. DenyHosts ist ein Tool, das Login-Versuche auf SSH überwacht. Wenn es wieder und wieder fehlgeschlagene Login-Versuche von der gleichen IP Adresse aufspürt, blockt DenyHosts weitere Login-Versuche diesr IP Adresse, indem es sie in /etc/hosts.deny packt. DenyHosts kann als Cron oder Daemon ausgeführt werden. In dieser Anleitung führe ich DenyHosts als Daemon aus.
Von der DenyHosts Webseite:
"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... "
Diese Anleitung basiert auf einem Debian Sarge System. Sie sollte jedoch mit ganz kleinen Veränderungen auch auf andere Distributionen passen.
Ich möchte an dieser Stelle darauf hinweisen, dass dies nicht der einzige Weg ist, ein solches System einzurichten. Es gibt viele Varianten, dieses Ziel zu erreichen - dies ist der Weg, den ich gewählt habe. Ich übernehme keine Garantie, dass dies auch bei Dir funktioniert!

1 Installation

DenyHosts ist in Python geschrieben, daher müssen wir zunächst Python und die Python Development Dateien installieren:
apt-get install python2.3-dev python2.3
Dann laden wir wie folgt DenyHosts runter und installieren es:
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

Damit wird DenyHosts in /usr/share/denyhosts installiert.

2 Konfiguration

Nun müssen wir die DenyHosts Konfigurationsdatei erstellen /usr/share/denyhosts/denyhosts.cfg. Dafür können wir die Muster-Konfigurationsdatei /usr/share/denyhosts/denyhosts.cfg-dist verwenden:
cd /usr/share/denyhosts
cp denyhosts.cfg-dist denyhosts.cfg

Dann müssen wir denyhosts.cfg mit einem unserer bevorzugten Editor wie zum Beispiel vi editieren. So sieht es bei mir aus:
       ############ 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 <nobody@localhost> 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 # #######################################################################

Pass auf, dass Du SECURE_LOG und LOCK_FILE den richtigen Werten Deiner Distribution zuweist! Bei Debian sind es diese:
SECURE_LOG = /var/log/auth.log
LOCK_FILE = /var/run/denyhosts.pid

Da DenyHosts als Daemon ausgeführt werden soll, benötigen wir das Daemon Kontroll-Skript /usr/share/denyhosts/daemon-control. Again, we can use the sample script /usr/share/denyhosts/daemon-control-dist um die benötigte Datei zu erstellen:
cp daemon-control-dist daemon-control
Bearbeite /usr/share/denyhosts/daemon-control und pass auf, dass Du die korrkten Werte für DENYHOSTS_BIN, DENYHOSTS_LOCK und DENYHOSTS_CFG einstellst. Bei Debian sind es diese:
DENYHOSTS_BIN = "/usr/bin/denyhosts.py"
DENYHOSTS_LOCK = "/var/run/denyhosts.pid"
DENYHOSTS_CFG = "/usr/share/denyhosts/denyhosts.cfg"

Meine /usr/share/denyhosts/daemon-control Datei sieht wie folgt aus:
#!/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()
Als Nächstes müssen wir veranlassen, dass diese Datei ausführbar wird:
chown root daemon-control
chmod 700 daemon-control

Danach erstellen wir die System Bootup Links für DenyHosts, damit es automatisch gestartet wird wenn das System hochfährt:
cd /etc/init.d
ln -s /usr/share/denyhosts/daemon-control denyhosts
update-rc.d denyhosts defaults

Letztendlich starten wir DenyHosts:
/etc/init.d/denyhosts start
DenyHosts loggt auf /var/log/denyhosts, falls Du an den Logs interessiert bist. Der SSH Daemon loggt auf /var/log/auth.log auf Debian. Du kannst beide Logs überwachen und versuchen, Dich mit einem ungültigen Benutzer oder mit einem gültigen Benutzer aber ungültigen Passwort, etc. via SSH anzumelden und beobachten was passiert. Nachdem Du den Grenzwert ungültiger Login-Versuche überschritten hast, sollte die IP Adresse, von der aus Du versucht hast Dich zu verbinden, in /etc/hosts.deny wie folgt aufgelistet werden:
# /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
Das bedeutet, dass sich das System mit der IP Adresse 192.168.0.203 nicht mehr über SSH verbinden kann.
Du kannst festlegen, ob/wenn IP Adressen wieder aus /etc/hosts.deny entfernt werden sollen - sieh Dir die PURGE_DENY Variable in /usr/share/denyhosts/denyhosts.cfg an. Du musst DenyHosts wie folgt mit der --purge Option starten, damit die PURGE_DENY Variable in Kraft treten kann:
/etc/init.d/denyhosts start --purge
Du kannst IP Adressen aber auch manuell entfernen. Sobald diese entfernt sind, können diese IP Adressen versuchen, sich erneut via SSH einzuloggen.
Links