Seguridad SSH · 15 min read · Jan 10, 2026

Prevención de Ataques de Diccionario SSH con DenyHosts

Prevención de Ataques de Diccionario SSH con DenyHosts

Versión 1.0
Autor: Falko Timme
Última edición: 02/07/2006

En este HowTo mostraré cómo instalar y configurar DenyHosts. DenyHosts es una herramienta que observa los intentos de inicio de sesión en SSH, y si encuentra intentos fallidos de inicio de sesión una y otra vez desde la misma dirección IP, DenyHosts bloquea más intentos de inicio de sesión desde esa dirección IP al ponerla en /etc/hosts.deny. DenyHosts se puede ejecutar mediante cron o como un daemon. En este tutorial ejecutaré DenyHosts como un daemon.

Desde el sitio web de DenyHosts:

“DenyHosts es un script destinado a ser ejecutado por administradores de sistemas Linux para ayudar a frustrar ataques al servidor ssh.

Si alguna vez has mirado tu registro ssh (/var/log/secure en Redhat, /var/log/auth.log en Mandrake, etc…) puedes alarmarte al ver cuántos hackers intentaron acceder a tu servidor. Esperemos que ninguno de ellos haya tenido éxito (pero, ¿cómo lo sabrías?). ¿No sería mejor prevenir automáticamente que ese atacante continúe accediendo a tu sistema?

DenyHosts intenta abordar lo anterior…”

Este tutorial se basa en un sistema Debian Sarge, sin embargo, debería aplicarse a otras distribuciones con casi ninguna modificación.

Quiero decir primero que esta no es la única forma de configurar un sistema así. Hay muchas maneras de lograr este objetivo, pero este es el camino que elijo. No emito ninguna garantía de que esto funcione para ti.

1 Instalación

DenyHosts está escrito en Python, por lo tanto, primero debemos instalar Python y también los archivos de desarrollo de Python:

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

Luego descargamos e instalamos DenyHosts de esta manera:

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

Esto instala DenyHosts en /usr/share/denyhosts.

2 Configuración

Ahora tenemos que crear el archivo de configuración de DenyHosts /usr/share/denyhosts/denyhosts.cfg. Podemos usar el archivo de configuración de muestra /usr/share/denyhosts/denyhosts.cfg-dist para esto:

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

Luego debemos editar denyhosts.cfg con nuestro editor favorito como vi, por ejemplo. El mío se ve así:

| ############ ESTAS CONFIGURACIONES SON REQUERIDAS ############ ######################################################################## # # SECURE_LOG: el archivo de registro que contiene información de registro de sshd # si no estás seguro, grep "sshd:" /var/log/* # # El archivo a procesar puede ser sobreescrito con el argumento de línea de comando --file # # Redhat o Fedora Core: #SECURE_LOG = /var/log/secure # # Mandrake, FreeBSD o OpenBSD: SECURE_LOG = /var/log/auth.log # # SuSE: #SECURE_LOG = /var/log/messages # ######################################################################## ######################################################################## # HOSTS_DENY: el archivo que contiene información de acceso restringido de hosts # # La mayoría de los sistemas operativos: HOSTS_DENY = /etc/hosts.deny # # Algunos BSD (FreeBSD) Unixes: #HOSTS_DENY = /etc/hosts.allow # # Otra posibilidad (también ver la siguiente opción): #HOSTS_DENY = /etc/hosts.evil ####################################################################### ######################################################################## # PURGE_DENY: elimina las entradas de HOSTS_DENY que son más antiguas que este tiempo # cuando DenyHosts es invocado con la bandera --purge # # el formato es: i[dhwmy] # Donde 'i' es un entero (ej. 7) # 'm' = minutos # 'h' = horas # 'd' = días # 'w' = semanas # 'y' = años # # nunca purgar: PURGE_DENY = # # purgar entradas más antiguas que 1 semana #PURGE_DENY = 1w # # purgar entradas más antiguas que 5 días #PURGE_DENY = 5d ####################################################################### ####################################################################### # BLOCK_SERVICE: el nombre del servicio que debe ser bloqueado en HOSTS_DENY # # man 5 hosts_access para detalles # # ej. sshd: 127.0.0.1 # bloqueará inicios de sesión sshd desde 127.0.0.1 # # Para bloquear todos los servicios para el host ofensivo: #BLOCK_SERVICE = ALL # Para bloquear solo sshd: BLOCK_SERVICE = sshd # Para solo registrar el host ofensivo y nada más (si se usa # un archivo auxiliar para listar los hosts). Referirse a: # http://denyhosts.sourceforge.net/faq.html#aux #BLOCK_SERVICE = # ####################################################################### ####################################################################### # # DENY_THRESHOLD_INVALID: bloquear cada host después de que el número de intentos de inicio de sesión fallidos # haya superado este valor. Este valor se aplica a intentos de inicio de sesión de usuario inválidos # (ej. cuentas de usuario no existentes) # DENY_THRESHOLD_INVALID = 5 # ####################################################################### ####################################################################### # # DENY_THRESHOLD_VALID: bloquear cada host después de que el número de fallidos # intentos de inicio de sesión haya superado este valor. Este valor se aplica a intentos de inicio de sesión de usuario válidos # (ej. cuentas de usuario que existen en /etc/passwd) excepto # para el usuario "root" # DENY_THRESHOLD_VALID = 10 # ####################################################################### ####################################################################### # # DENY_THRESHOLD_ROOT: bloquear cada host después de que el número de fallidos # intentos de inicio de sesión haya superado este valor. Este valor se aplica a # intentos de inicio de sesión del usuario "root" únicamente. # DENY_THRESHOLD_ROOT = 5 # ####################################################################### ####################################################################### # # WORK_DIR: la ruta que DenyHosts usará para escribir datos # (se creará si no existe ya). # # Nota: se recomienda que uses una ruta absoluta # para este valor (ej. /home/foo/denyhosts/data) # WORK_DIR = /usr/share/denyhosts/data # ####################################################################### ####################################################################### # # SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS # # SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS=YES|NO # Si se establece en YES, si un intento de inicio de sesión sospechoso resulta de un host permitido # entonces se considera sospechoso. Si esto es NO, entonces los inicios de sesión sospechosos # de hosts permitidos no se informarán. Todos los inicios de sesión sospechosos de # direcciones IP que no están en hosts permitidos siempre se informarán. # SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS=YES ###################################################################### ###################################################################### # # HOSTNAME_LOOKUP # # HOSTNAME_LOOKUP=YES|NO # Si se establece en YES, para cada dirección IP que es reportada por Denyhosts, # se buscará el nombre de host correspondiente y se informará también # (si está disponible). # HOSTNAME_LOOKUP=YES # ###################################################################### ###################################################################### # # LOCK_FILE # # LOCK_FILE=/path/denyhosts # Si este archivo existe cuando se ejecuta DenyHosts, entonces DenyHosts saldrá # inmediatamente. De lo contrario, este archivo se creará al invocarlo # y se eliminará al salir. Esto asegura que solo una instancia esté # en ejecución a la vez. # # Redhat/Fedora: #LOCK_FILE = /var/lock/subsys/denyhosts # # Debian LOCK_FILE = /var/run/denyhosts.pid # # Varios #LOCK_FILE = /tmp/denyhosts.lock # ###################################################################### ############ ESTAS CONFIGURACIONES SON OPCIONALES ############ ####################################################################### # # ADMIN_EMAIL: si deseas recibir correos electrónicos sobre nuevos # hosts restringidos e inicios de sesión sospechosos, establece esta dirección para # que coincida con tu dirección de correo electrónico. Si no deseas recibir estos informes # deja este campo en blanco (o ejecuta con la opción --noemail) # ADMIN_EMAIL = # ####################################################################### ####################################################################### # SMTP_HOST = localhost SMTP_PORT = 25 SMTP_FROM = DenyHosts SMTP_SUBJECT = Informe de DenyHosts #SMTP_USERNAME=foo #SMTP_PASSWORD=bar # ####################################################################### ###################################################################### # # ALLOWED_HOSTS_HOSTNAME_LOOKUP # # ALLOWED_HOSTS_HOSTNAME_LOOKUP=YES|NO # Si se establece en YES, para cada entrada en el archivo WORK_DIR/allowed-hosts, # se buscará el nombre de host. Si tus versiones de tcp_wrappers # y sshd a veces registran nombres de host además de direcciones IP # entonces puedes desear especificar esta opción. # #ALLOWED_HOSTS_HOSTNAME_LOOKUP=NO # ###################################################################### ###################################################################### # # AGE_RESET_VALID: Especifica el período de tiempo entre intentos de inicio de sesión fallidos # que, al ser superado, resultará en que el conteo fallido para # este host se restablezca a 0. Este valor se aplica a intentos de inicio de sesión # para todos los usuarios válidos (aquellos dentro de /etc/passwd) con el # excepción de root. Si no se define, este conteo nunca # se restablecerá. # # Ver los comentarios en la sección PURGE_DENY (arriba) # para detalles sobre cómo especificar este valor o para detalles completos # referirse a: http://denyhosts.sourceforge.net/faq.html#timespec # AGE_RESET_VALID=5d # ###################################################################### ###################################################################### # # AGE_RESET_ROOT: Especifica el período de tiempo entre intentos de inicio de sesión fallidos # que, al ser superado, resultará en que el conteo fallido para # este host se restablezca a 0. Este valor se aplica a todos los intentos de inicio de sesión # a la cuenta de usuario "root". Si no se define, # este conteo nunca se restablecerá. # # Ver los comentarios en la sección PURGE_DENY (arriba) # para detalles sobre cómo especificar este valor o para detalles completos # referirse a: http://denyhosts.sourceforge.net/faq.html#timespec # AGE_RESET_ROOT=25d # ###################################################################### ###################################################################### # # AGE_RESET_INVALID: Especifica el período de tiempo entre intentos de inicio de sesión fallidos # que, al ser superado, resultará en que el conteo fallido para # este host se restablezca a 0. Este valor se aplica a intentos de inicio de sesión # realizados a cualquier nombre de usuario inválido (aquellos que no aparecen # en /etc/passwd). Si no se define, el conteo nunca se restablecerá. # # Ver los comentarios en la sección PURGE_DENY (arriba) # para detalles sobre cómo especificar este valor o para detalles completos # referirse a: http://denyhosts.sourceforge.net/faq.html#timespec # AGE_RESET_INVALID=10d # ###################################################################### ###################################################################### # # PLUGIN_DENY: Si se establece, este valor debe apuntar a un ejecutable # programa que será invocado cuando un host sea agregado al # archivo HOSTS_DENY. Este ejecutable recibirá el host # que será agregado como su único argumento. # #PLUGIN_DENY=/usr/bin/true # ###################################################################### ###################################################################### # # PLUGIN_PURGE: Si se establece, este valor debe apuntar a un ejecutable # programa que será invocado cuando un host sea eliminado del # archivo HOSTS_DENY. Este ejecutable recibirá el host # que se va a purgar como su único argumento. # #PLUGIN_PURGE=/usr/bin/true # ###################################################################### ###################################################################### # # USERDEF_FAILED_ENTRY_REGEX: si se establece, este valor debe contener # una expresión regular que se puede usar para identificar hackers adicionales # para tu configuración ssh particular. Esta funcionalidad # extiende las expresiones regulares incorporadas que usa DenyHosts. # Este parámetro se puede especificar múltiples veces. # Ver esta entrada de faq para más detalles: # http://denyhosts.sf.net/faq.html#userdef_regex # #USERDEF_FAILED_ENTRY_REGEX= # ###################################################################### ######### ESTAS CONFIGURACIONES SON ESPECÍFICAS PARA EL MODO DAEMON ########## ####################################################################### # # DAEMON_LOG: cuando DenyHosts se ejecuta en modo daemon (--daemon flag) # este es el archivo de registro que usa DenyHosts para informar su estado. # Para deshabilitar el registro, deja en blanco. (el valor predeterminado es: /var/log/denyhosts) # DAEMON_LOG = /var/log/denyhosts # # deshabilitar el registro: #DAEMON_LOG = # ###################################################################### ####################################################################### # # DAEMON_LOG_TIME_FORMAT: cuando DenyHosts se ejecuta en modo daemon # (--daemon flag) esto especifica el formato de marca de tiempo de # los mensajes de DAEMON_LOG (el valor predeterminado es el formato ISO8061: # es decir, 2005-07-22 10:38:01,745) # # para posibles valores para este parámetro refiérase a: man strftime # # 1 de enero 13:05:59 #DAEMON_LOG_TIME_FORMAT = %b %d %H:%M:%S # # 1 de enero 01:05:59 #DAEMON_LOG_TIME_FORMAT = %b %d %I:%M:%S # ###################################################################### ####################################################################### # # DAEMON_LOG_MESSAGE_FORMAT: cuando DenyHosts se ejecuta en modo daemon # (--daemon flag) esto especifica el formato del mensaje de cada entrada registrada. # Por defecto se usa el siguiente formato: # # %(asctime)s - %(name)-12s: %(levelname)-8s %(message)s # # Donde la parte "% (asctime)s" se expande al formato # definido por DAEMON_LOG_TIME_FORMAT # # Esta cadena se pasa al constructor logging.Formatter de python. # Para detalles sobre los posibles tipos de formato, consulte: # http://docs.python.org/lib/node357.html # # Este es el predeterminado: #DAEMON_LOG_MESSAGE_FORMAT = %(asctime)s - %(name)-12s: %(levelname)-8s %(message)s # # ###################################################################### ####################################################################### # # DAEMON_SLEEP: cuando DenyHosts se ejecuta en modo daemon (--daemon flag) # esta es la cantidad de tiempo que DenyHosts dormirá entre la consulta # del SECURE_LOG. Ver los comentarios en la sección PURGE_DENY (arriba) # para detalles sobre cómo especificar este valor o para detalles completos # referirse a: http://denyhosts.sourceforge.net/faq.html#timespec # DAEMON_SLEEP = 30s # ####################################################################### ####################################################################### # # DAEMON_PURGE: ¿Con qué frecuencia debe DenyHosts, cuando se ejecuta en modo daemon, # ejecutar el mecanismo de purga para expirar entradas antiguas en HOSTS_DENY? # Esto no tiene efecto si PURGE_DENY está en blanco. # DAEMON_PURGE = 1h # ####################################################################### ######### ESTAS CONFIGURACIONES SON ESPECÍFICAS PARA ########## ######### SINCRONIZACIÓN DE DAEMON ########## ####################################################################### # # El modo de sincronización permite al daemon DenyHosts la capacidad # de enviar y recibir periódicamente datos de hosts denegados de tal manera que # los daemons DenyHosts en todo el mundo pueden informarse automáticamente # sobre hosts prohibidos. Este modo está deshabilitado por # defecto, debes descomentar SYNC_SERVER para habilitar este modo. # # para más información, por favor refiérase a: # http:/denyhosts.sourceforge.net/faq.html#sync # ####################################################################### ####################################################################### # # SYNC_SERVER: El servidor central que se comunica con los daemons DenyHost # Actualmente, denyhosts.net es el único servidor disponible # sin embargo, en el futuro, puede ser posible que las organizaciones # instalen su propio servidor para la sincronización de redes internas # # Para deshabilitar la sincronización (el valor predeterminado), no hagas nada. # # Para habilitar la sincronización, debes descomentar la siguiente línea: #SYNC_SERVER = http://xmlrpc.denyhosts.net:9911 # ####################################################################### ####################################################################### # # SYNC_INTERVAL: el intervalo de tiempo para realizar sincronizaciones si # SYNC_SERVER ha sido descomentado. El valor predeterminado es 1 hora. # #SYNC_INTERVAL = 1h # ####################################################################### ####################################################################### # # SYNC_UPLOAD: ¿permitir que tu daemon DenyHosts transmita hosts que han # sido denegados? Esta opción solo se aplica si SYNC_SERVER ha # sido descomentado. # #SYNC_UPLOAD = no # # el valor predeterminado: #SYNC_UPLOAD = yes # ####################################################################### ####################################################################### # # SYNC_DOWNLOAD: ¿permitir que tu daemon DenyHosts reciba hosts que han # sido denegados por otros? Esta opción solo se aplica si SYNC_SERVER ha # sido descomentado. # #SYNC_DOWNLOAD = no # # el valor predeterminado: #SYNC_DOWNLOAD = yes # ####################################################################### ####################################################################### # # SYNC_DOWNLOAD_THRESHOLD: Si SYNC_DOWNLOAD está habilitado, este parámetro # filtra los hosts devueltos a aquellos que han sido bloqueados tantas veces # por otros. Es decir, si se establece en 1, entonces si un solo servidor DenyHosts # ha denegado una dirección IP, entonces recibirás el host denegado. # #SYNC_DOWNLOAD_THRESHOLD = 10 # # el valor predeterminado: #SYNC_DOWNLOAD_THRESHOLD = 3 # ####################################################################### |

Asegúrate de establecer SECURE_LOG y LOCK_FILE en los valores correctos para tu distribución. Para Debian, estos son:

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

Como queremos ejecutar DenyHosts como un daemon, necesitamos el script de control del daemon /usr/share/denyhosts/daemon-control. Nuevamente, podemos usar el script de muestra /usr/share/denyhosts/daemon-control-dist para crear el archivo necesario:

cp daemon-control-dist daemon-control

Edita /usr/share/denyhosts/daemon-control y asegúrate de establecer los valores correctos para DENYHOSTS_BIN, DENYHOSTS_LOCK y DENYHOSTS_CFG. Para Debian, estos son:

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

Así que mi /usr/share/denyhosts/daemon-control se ve así:

| #!/usr/bin/env python # denyhosts Iniciar/detener el daemon DenyHosts # # chkconfig: 2345 98 02 # descripción: Activa/Desactiva el # daemon DenyHosts para bloquear intentos ssh # ############################################### ############################################### #### Edita esto para adaptarlo a tu configuración #### ############################################### DENYHOSTS_BIN = "/usr/bin/denyhosts.py" DENYHOSTS_LOCK = "/var/run/denyhosts.pid" DENYHOSTS_CFG = "/usr/share/denyhosts/denyhosts.cfg" ############################################### #### No edites abajo #### ############################################### import os, sys, signal, time STATE_NOT_RUNNING = -1 STATE_LOCK_EXISTS = -2 def usage(): print "Uso: %s {start [args...] | stop | restart [args...] | status | debug | condrestart [args...] }" % sys.argv[0] print print "Para una lista de 'args' válidos, refiérete a:" 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 "iniciando DenyHosts: ", cmd os.system(cmd) def stop(): pid = getpid() if pid >= 0: os.kill(pid, signal.SIGTERM) print "enviado DenyHosts SIGTERM" else: print "DenyHosts no está en ejecución" def debug(): pid = getpid() if pid >= 0: os.kill(pid, signal.SIGUSR1) print "enviado DenyHosts SIGUSR1" else: print "DenyHosts no está en ejecución" def status(): pid = getpid() if pid == STATE_LOCK_EXISTS: print "%s existe pero DenyHosts no está en ejecución" % DENYHOSTS_LOCK elif pid == STATE_NOT_RUNNING: print "Denyhosts no está en ejecución" else: print "DenyHosts está en ejecución con 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() |

A continuación, debemos hacer que ese archivo sea ejecutable:

chown root daemon-control
chmod 700 daemon-control

Después, creamos los enlaces de inicio del sistema para DenyHosts para que se inicie automáticamente cuando se inicie el sistema:

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

Finalmente, iniciamos DenyHosts:

/etc/init.d/denyhosts start

DenyHosts registra en /var/log/denyhosts, si estás interesado en los registros. El daemon SSH registra en /var/log/auth.log en Debian. Puedes observar ambos registros e intentar iniciar sesión con un usuario inválido o con un usuario válido y una contraseña incorrecta, etc. a través de SSH y ver qué sucede. Después de haber cruzado el umbral de intentos de inicio de sesión incorrectos, la dirección IP desde la cual intentaste conectarte debería aparecer en /etc/hosts.deny, así:

| # /etc/hosts.deny: lista de hosts que _no_ están permitidos para acceder al sistema. # Ver las páginas manuales hosts_access(5), hosts_options(5) # y /usr/doc/netbase/portmapper.txt.gz # # Ejemplo: ALL: some.host.name, .some.domain # ALL EXCEPT in.fingerd: other.host.name, .other.domain # # Si vas a proteger el portmapper, usa el nombre "portmap" para el # nombre del daemon. Recuerda que solo puedes usar la palabra clave "ALL" y direcciones IP # (NO nombres de host o nombres de dominio) para el portmapper. Ver portmap(8) # y /usr/doc/portmap/portmapper.txt.gz para más información. # # El comodín PARANOID coincide con cualquier host cuyo nombre no coincide con su # dirección. # Puede que desees habilitar esto para asegurar que cualquier programa que no # valide nombres de host buscados aún deje registros comprensibles. En versiones pasadas # de Debian, este ha sido el valor predeterminado. # ALL: PARANOID sshd: 192.168.0.203 |

Esto significa que el sistema con la dirección IP 192.168.0.203 ya no puede conectarse usando SSH.

Puedes especificar si/cuándo se eliminan las direcciones IP nuevamente de /etc/hosts.deny - echa un vistazo a la variable PURGE_DENY en /usr/share/denyhosts/denyhosts.cfg. Debes iniciar DenyHosts con la opción –purge para hacer efectiva la variable PURGE_DENY, así:

/etc/init.d/denyhosts start –purge

Sin embargo, también puedes eliminar direcciones IP manualmente de allí, y tan pronto como se hayan eliminado, estas direcciones IP pueden intentar iniciar sesión nuevamente a través de SSH.

Enlaces

Share: X/Twitter LinkedIn

Recibe nuevas publicaciones en tu bandeja de entrada.

No spam. Cancela la suscripción en cualquier momento.