Asegurando SSH con fail2ban y ufw

Como todo usuario avanzado de GNU/Linux, mantengo varios servidores en casa dedicados a diferentes servicios y me muevo entre ellos con la ayuda de SSH. De todos ellos, hay uno que siempre está expuesto a internet y de más está decir que es un riesgo grande exponerlo ya que algún día a alguien se le puede ocurrir realizar un ataque automatizado sobre él y causar una denegación de servicio en el mejor de los casos o si se diera el caso de la existencia de una vulnerabilidad grave que no pueda actualizar a tiempo podría alguien obtener acceso remoto a mi red local.

Para la situación anterior, más vale estar prevenido así que esta entrada va sobre acciones de hardening enfocadas al servidor SSH utilizando fail2ban y el firewall ufw. Como configuración base he seguido estos consejos de Linux Audit que dejan como una roca sólida la configuración de SSH.

Lo primero, actualizar la distro, es la primera medida de seguridad mantener toda la paquetería al día. En mi caso con Debian o igual con sus derivados, la rutina es la misma:

$ sudo apt update

Luego instalamos ufw y fail2ban

$ sudo apt install ufw fail2ban

A algunas personas les gusta cambiar el puerto de SSH por uno diferente como medida de seguridad. Esto es una medida de seguridad a través de la oscuridad ya que no es completamente efectiva, pues es completamente vulnerable a un escaneo y nos deja en la misma posición del principio; sin embargo, en mi experiencia cambiar de puerto reduce en gran medida los ataques automatizados.

Configurando fail2ban

Lo siguiente será configurar fail2ban. La configuración es preferible hacerla en un archivo jail.local y no tocar el archivo jail.conf.

$ cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Ahora al archivo jail.local añadimos nuestra configuración:

1
2
3
4
5
6
7
8
9
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime = 1h
findtime = 10m
maxretry = 3

[sshd]
enabled = true
action = ufw[application="ssh", blocktype=reject]

Destripando un poco lo anterior, en ignoreip añadí el localhost, para no quedarme por fuera, bantime se refiere al tiempo que va a estar bloqueada una ip, findtime el tiempo en el que deben ocurrir los fallos y maxretry el número de fallos, estos datos son completamente ajustables.

Configurando ufw

Ahora vamos con ufw. Primero las reglas predeterminadas:

$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing

Ahora vamos con las reglas de las aplicaciones, en mi caso particular me interesa SSH

$ sudo ufw allow ssh

Pero podemos añadir más aplicaciones o servicios como servidores web, de base de datos y muchos otros. Así como también definir puertos específicos o filtrar según red. Si queremos bloquear según el nombre de aplicación, podemos obtener una lista así:

$ sudo ufw app list

Esto mostrara un listado donde solo bastará colocar ufw enable %aplicacion% para activarla en el firewall.

Activando y probando

Una vez hemos definido las reglas de ufw podemos activarlo e iniciarlo.

$ sudo ufw enable

Ahora podemos activar fail2ban:

$ sudo systemctl enable fail2ban
$ sudo systemctl start fail2ban

Podemos hacer pruebas y verificar que ocurra el bloqueo, si verificamos el status de ufw y fail2ban obtendremos sendas salidas:

$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 1
|  |- Total failed: 10
|  `- File list:    /var/log/auth.log
`- Actions
   |- Currently banned: 1
   |- Total banned: 1
   `- Banned IP list:   192.168.1.123
$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
SSH                        REJECT      192.168.1.123            
22/tcp                     ALLOW       Anywhere                  
80/tcp                     ALLOW       Anywhere                  
22/tcp (v6)                ALLOW       Anywhere (v6)            
80/tcp (v6)                ALLOW       Anywhere (v6)

Hasta este punto podríamos terminar, pero fail2ban tiene muchas más opciones que podemos aprovechar, por ejemplo que nos envíe un correo electrónico cuando ocurre una incidencia, pero aún podemos ir más allá creando unos bots que nos anuncien cuando una ip es baneada.

Creando un bot con Slack

Lo primero será crear y añadir una app de Slack donde podramos añadir nuestro bot que será del tipo incoming webhook. Solo hay que seguir la guía de Slack. Al final tendrás un bot añadido esperando respuestas que postea directamente a un canal de Slack. Solo como detalle, para que el script funcione correctamente, necesita fail2ban versión 0.10 o superior.

Ahora añadimos la acción de fail2ban, creando un archivo en /etc/fail2ban/action.d/slack-notify.conf con el contenido que se muestra abajo. El script original lo pueden ver aquí, es posible que con las correcciones de formato que hace WordPress, el código pueda mostrar errores así que preferiblemente en la fuente para librarse de esto. Es importante destacar que esta acción depende de que curl este instalado.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#
# Author: Michael Lehmann
#

[Definition]

#(optional) Prevent notification/action for re-banned IPs when Fail2Ban restarts.
norestored = 1

# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# Values:  CMD
#
# original  # actionstart = curl -X POST -H 'Content-type: application/json' --data '{"text":"Fail2Ban (<name>) jail has started"}' <slack_webhook_url>
# one-liner # actionstart = curl -X POST -H 'Content-type: application/json' --data "{"text":"[$(hostname)] Fail2Ban (<name>) jail has started"}" <slack_webhook_url>
actionstart = curl -X POST -H 'Content-type: application/json' --data "{"text":"[<host_name>] Fail2Ban (<name>) jail has started"}" <slack_webhook_url>

# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
#
# original # actionstop = curl -X POST -H 'Content-type: application/json' --data '{"text":"Fail2Ban (<name>) jail has stopped"}' <slack_webhook_url>
# one-liner # actionstop = curl -X POST -H 'Content-type: application/json' --data "{"text":"[$(hostname)] Fail2Ban (<name>) jail has stopped"}" <slack_webhook_url>
actionstop = curl -X POST -H 'Content-type: application/json' --data "{"text":"[<host_name>] Fail2Ban (<name>) jail has stopped"}" <slack_webhook_url>

# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# Values:  CMD
#
actioncheck =

# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
# original  # actionban = curl -X POST -H 'Content-type: application/json' --data '{"text":"Fail2Ban (<name>) banned IP *<ip>* for <failures> failure(s)"}' <slack_webhook_url>
# one-liner # actionban = curl ipinfo.io/<ip>/country | (read COUNTRY; curl -X POST -H 'Content-type: application/json' --data "{"text":"[$(hostname)] Fail2Ban (<name>) banned IP *<ip>* :flag-$COUNTRY: ($COUNTRY) "}" <slack_webhook_url>)
actionban = curl -X POST -H 'Content-type: application/json' --data "{"text":"[<host_name>] Fail2Ban (<name>) banned IP *<ip>* :flag-<country_name>: (<country_name>) "}" <slack_webhook_url>

# Option:  actionunban
# Notes.:  command executed when unbanning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
# original # actionunban = curl -X POST -H 'Content-type: application/json' --data '{"text":"Fail2Ban (<name>) unbanned IP *<ip>*"}' <slack_webhook_url>
# one-liner # actionunban = curl ipinfo.io/<ip>/country | (read COUNTRY; curl -X POST -H 'Content-type: application/json' --data "{"text":"[$(hostname)] Fail2Ban (<name>) unbanned IP *<ip>* :flag-$COUNTRY: ($COUNTRY) "}" <slack_webhook_url>)
actionunban = curl -X POST -H 'Content-type: application/json' --data "{"text":"[<host_name>] Fail2Ban (<name>) unbanned IP *<ip>* :flag-<country_name>: (<country_name>) "}" <slack_webhook_url>

[Init]

init = 'Sending notification to Slack'

slack_webhook_url = <Add_Your_Full_Slack_Webhook_URL_Here>

#The following are variables that will be evaluated at runtime in bash, thus cannot be used inside of single-quotes
host_name = $(hostname)

# lets find out from what country we have our hacker
country_name = $(curl ipinfo.io/<ip>/country)

El único cambio que hay que hacer es añadir la URI del webhook, donde se encuentra la variable slack_webhook_url. Ahora añadimos la acción al archivo jail.local.

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime = 1h
findtime = 10m
maxretry = 3

[sshd]
enabled = true
action = ufw[application="ssh", blocktype=reject]
         slack-notify

Una vez hemos hecho lo anterior, solo queda reiniciar. Al hacerlo, debemos recibir una notificación en Slack en el canal que elegimos sobre la jaula que ha iniciado. Seguidamente también cualquier bloqueo que haga fail2ban.

$ sudo systemctl restart fail2ban

Por último, fail2ban se puede integrar con Telegram de un modo similar a como se acaba de hacer con Slack. Más adelante haré una entrada sobre como hacer esta integración.

Quedando fuera…

Si por casualidad te baneas a ti mismo y tienes aún una conexión activa al servidor o física, para desbloquear el procedimiento es el siguiente:

$ sudo fail2ban-client set nombredejaula unbanip ladireccionip

Lo que sería algo así:

$ sudo fail2ban-client set sshd unbanip 192.168.1.123

avatar
Moisés Serrano Samudio Médico de atención primaria, fotógrafo aficionado, apasionado de las tecnologías relacionadas con el EdTech y el eHealth y diseñador/desarrollador de sitios web de salud. Médico, apasionado del EdTech/eHealth y diseñador/desarrollador de sitios web de salud.
  1. Aún no hay comentarios...

Deja una respuesta

Su email no será publicado. Required fields are marked *