Seguridad básica en tu VPS
Índice
Cuando en AWS arrancamos una máquina, por ej. desde una AMI de «Amazon Linux 2» contamos con ciertos elementos de seguridad, como el uso por defecto de SSH keys en lugar de usar una contraseña root. Además de estar detrás de un Firewall.
Hace tiempo que tenía ganas de probar un proveedor Cloud, llamado Hetzner el cual ofrece VPS a precios bajísimos con buenos recursos. El único problema que le veo es que aquí no contamos con los «Grupos de seguridad» y «ACLs» de AWS.
Podemos arrancar una máquina desde un «snapshot» personal, pero la primera vez no tenemos ninguno, por lo que nuestras opciones son elegir una de las siguientes imágenes por defecto.
Sí que es cierto que una vez tenemos el servidor en marcha, podemos montarle una ISO de alguna distro para realizar una instalación desde cero.
En nuestro caso seleccionamos Ubuntu 20.04 con el tamaño de instancia CX11 (2GB RAM, 1vCPU, 20GB SSD, 20TB transferencia por 3,01€)
Durante el aprovisionamiento observamos una advertencia, y es que aunque recomiendan usar una SSH key, nos da la posibilidad de no usarla, enviándonos por e-mail la contraseña root; lo que sería empezar con muy mal pie.
¡No passwords!
Generamos nuestro par de clave pública/privada (Hay varias formas, según el SO en el que estés…openssl, Putty, MobaXterm, etc)
En el caso de MobaXterm, nos facilita la clave pública con formato OpenSSH. Esta clave la pegaríamos en el apartado «Add an SSH key» de Hetzner. Eso hará que automáticamente nuestra máquina tenga la clave en /root/.ssh/authorized_keys lo que permitirá la conexión.
No te olvides de descargar la clave privada.
En unos segundos se aprovisionará el servidor. Y podremos acceder vía ssh.
ssh -i private_key.ppk root@laipquenosasignaron
Securizar sshd
Lo primero que vamos a hacer son unos ajustes del sshd para mejorar la seguridad de este.
Crear nuevo usuario y desactivar acceso root.
Vamos a crear un usuario con permisos y desactivar el acceso root en cuanto este esté operativo.
Añadimos un usuario
sudo adduser usuario
Crearemos la contraseña por el camino; de no ser así, ejecutamos:
sudo passwd usuario
Añadimos el usuario al sudoers:
sudo visudo
# User privilege specification root ALL=(ALL:ALL) ALL usuario ALL=(ALL:ALL) ALL
Aprovechando que el usuario root ya tiene la clave pública de mi certificado, y que vamos a deshabilitar el acceso root, voy a migrar esta al nuevo usuario.
mkdir /home/usuario/.ssh chmod 700/home/usuario/.ssh cp /root/.ssh/authorized_keys /home/usuario/.ssh/authorized_keys chmod 600 /home/usuario/.ssh/authorized_keys chown -R usuario. /home/usuario/.ssh/
Con esto hemos migrado la clave pública al nuevo usuario, y le hemos dado los permisos correctos.
¡Importante! Acceder con este nuevo usuario y probar que tenemos efectivamente acceso root, pues en el siguiente paso, desactivaremos el inicio de sesión root.
Desactivamos el inicio de sesión para root
Editamos /etc/ssh/sshd_config
PermitRootLogin no
Configurar tiempo para una desconexión de la sesión SSH
ClientAliveInterval 300 ClientAliveCountMax 1
Habilitar SSH solo para nuestro usuario
AllowUsers usuario
Deshabilitar la autenticación mediante contraseñas
¡Suponiendo que estés usando certificados!
PasswordAuthentication no
PubkeyAuthentication yes
Cambiar puerto de escucha SSH por defecto
El puerto por defecto es el 22, por lo que será el que más sondeen los chinos/rusos bots.
Aunque si tienes o vas a activar el firewall (como veremos más adelante) quizás te de igual dejar el por defecto. A mi me gusta cambiarlo. [Si lo cambias revisa antes que no tengas el firewall activado, no vaya a ser que te quedes fuera del coche con la llave dentro 🙁 ]
Port 22
Número de intentos fallidos para que el servidor SSH te desconecte.
MaxAuthTries 2
Desactivar funciones no utilizadas
Si sabemos que no vamos a utilizar ciertas funciones, podemos desactivarlas parar restarle oportunidades a los malignos.
AllowTcpForwarding no # Disables port forwarding. X11Forwarding no # Disables remote GUI view. AllowAgentForwarding no # Disables the forwarding of the SSH login. AuthorizedKeysFile .ssh/authorized_keys # The ".ssh/authorized_keys2" file should be removed.
Aplicar los cambios
Verificamos la configuración ssh
sshd -t
Si no se encontraron errores entonces, reiniciamos el servicio sshd
systemctl restart sshd
Si cambiamos el puerto de escucha, acuérdate de esto cuando vuelvas a conectarte.
ssh -p puerto -i private_key.ppk usuario@laipquenosasignaron
Protección contra Ataques de Fuerza Bruta (Fail2Ban)
Fail2ban es una aplicación escrita en Python que ayuda a proteger un servidor de la fuerza bruta y los ataques de denegación de servicios (DDoS).
Analiza los logs y cuando detecta varios intentos fallidos de conexión, penaliza o bloquea a la IP maligna.
Hetzner dispone de un sistema que detecta y bloquea ataques DDOS, pero no viene mal un extra de seguridad.
Instalación de Fail2ban
apt install fail2ban systemctl enable fail2ban
Configuración de Fail2ban
Fail2Ban viene configurado por defecto para proteger sshd, pero podemos ampliar su utilidad creando ficheros de configuración en este directorio:
/etc/fail2ban/jail.d/
Por ejemplo:
[sshd] enabled = true port = 3333 filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 3600
[apache] enabled = true port = http,https filter = apache-auth logpath = /var/log/apache2/*error.log maxretry = 3 bantime = 600 [apache-overflows] enabled = true port = http,https filter = apache-overflows logpath = /var/log/apache2/*error.log maxretry = 3 bantime = 600 [apache-noscript] enabled = true port = http,https filter = apache-noscript logpath = /var/log/apache2/*error.log maxretry = 3 bantime = 600 [apache-badbots] enabled = true port = http,https filter = apache-badbots logpath = /var/log/apache2/*error.log maxretry = 3 bantime = 600 [http-get-dos] enabled = true port = http,https filter = http-get-dos logpath = /var/log/apache2/*access.log maxretry = 400 findtime = 400 bantime = 200 action = iptables[name=HTTP, port=http, protocol=tcp]
- [apache] Bloquea los host remotos con mas de 3 intentos fallidos de autenticación.
- [apache-overflows] Bloquea host remotos que realicen peticiones de URLs sospechosas.
- [apache-noscript] Bloquea host remotos que busquen scripts para ejecutar en el sitio web.
- [apache-badbots] Bloquea host remotos que traten de solicitar bot maliciosos
- [http-get-dos] Detiene ataques DDOS
Tenemos que crear también la expresión regular en el fichero http-get-dos.conf
nano /etc/fail2ban/filter.d/http-get-dos.conf
[Definition] # Option: failregex # Note: This regex will match any GET entry in your logs, so basically all valid and not valid entries are a match. # You should set up in the jail.conf file, the maxretry and findtime carefully in order to avoid false positives. failregex = ^<HOST> -.*"(GET|POST).* # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. # Values: TEXT ignoreregex =
Para aplicar los cambios:
systemctl restart fail2ban
Podemos ver el estado de nuestras jaulas con:
fail2ban-client status
Firewall (ufw/iptables)
Aquí no contamos con los “Grupos de seguridad” de AWS, por lo que creo, que es muy necesario configurar un firewall. Yo he utilizado ufw (Uncomplicated Firewall) para editar de manera muy rápida las reglas iptables. A los pocos minutos de tenerlo activado podremos apreciar en /var/log/syslog como bloquea conexiones no deseadas.
Comprobamos su estado actual
ufw status
En mi caso estaba desactivado. Por lo que procedo a configurarlo, y luego activarlo. Queremos permitir el tráfico http y https, el de ssh y el del agente de checkmk, con el que monitorizamos la máquina. Deberías ajustar esto a tus necesidades claro.
ufw allow http ufw allow https ufw allow 6556/tcp ufw allow 22/tcp (o el puerto en el que esté escuchando sshd)
Una vez configurado lo habilitamos y comprobamos de nuevo el estado.
Nos avisará de que podríamos ser desconectados de la sesión, pero esto no pasará si configuramos la regla apropiada para el puerto de escucha ssh.
ufw enable ufw status
Permitir SSH solo a la IP de mi casa (IP dinámica)
Las reglas para checkmk (6556/tcp) y para la conexión ssh me gustaría que no estuvieran abiertas al mundo, que únicamente estuvieran permitidas a mi IP. Para esto:
- Nos agendamos un proveedor de DNS dinámico (DDNS) gratuito. Yo utilizo https://www.duckdns.org/ De esta manera tendremos una dirección dns que apuntará todo el tiempo a la IP de casa/oficina. En mi caso tengo configurado un cron en la raspberry que se encarga de mantener actualizado el registro de duckdns.
- Configuramos otro cron en nuestro VPS que lance un script encargado de verificar que IP responde a la dirección DDNS; para seguidamente modificar las reglas del firewall.
En este hilo encontré el script, que modifique a mi gusto. Concretamente la versión que se encuentra en uno de los últimos comentarios:
/etc/iptables_update.sh
#!/bin/bash HOSTNAME=YOUR.DNS.NAME.HERE if [[ $EUID -ne 0 ]]; then echo "This script must be run as root" exit 1 fi new_ip=$(host $HOSTNAME | head -n1 | cut -f4 -d ' ') old_ip=$(/usr/sbin/ufw status | grep $HOSTNAME | head -n1 | tr -s ' ' | cut -f3 -d ' ') if [ "$new_ip" = "$old_ip" ] ; then echo IP address has not changed else if [ -n "$old_ip" ] ; then /usr/sbin/ufw delete allow from $old_ip to any fi /usr/sbin/ufw allow from $new_ip to any comment $HOSTNAME echo iptables have been updated fi
Pero modificando la última parte para permitir los puertos que me interesaban, y súper importante, realizando una comprobación previa, para ver si el valor que devuelve «host» es una IP válida.
Esta comprobación la añadí a posteriori, al darme cuenta de que en ocasiones los servidores DDNS de Duckdns fallan y tardan un rato en responder, por lo que no responden, y el resultado de:
host $HOSTNAME | head -n1 | cut -f4 -d ' '
sería «found» o simplemente nada, por lo que, el script original, aunque no podría añadir una nueva regla a iptables con ese valor, pero sí que eliminaría la antigua regla, dejándonos sin acceso hasta que los servidores DDNS de Duckdns volvieran a funcionar.
Con esta comprobación, problema solucionado.
#!/bin/bash function valid_ip() { local ip=$1 local stat=1 if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then OIFS=$IFS IFS='.' ip=($ip) IFS=$OIFS [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] stat=$? fi return $stat } HOSTNAME=YOUR.DNS.NAME.HERE if [[ $EUID -ne 0 ]]; then echo "This script must be run as root" exit 1 fi new_ip=$(host $HOSTNAME | head -n1 | cut -f4 -d ' ') old_ip=$(/usr/sbin/ufw status | grep $HOSTNAME | head -n1 | tr -s ' ' | cut -f3 -d ' ') if ! valid_ip $new_ip; then exit 1 fi if [ "$new_ip" = "$old_ip" ] ; then exit 0 else if [ -n "$old_ip" ] ; then /usr/sbin/ufw delete allow from $old_ip to any port 22,6556 proto tcp fi /usr/sbin/ufw allow from $new_ip to any port 22,6556 proto tcp comment $HOSTNAME echo "Se ha modificado iptables por cambio de ip $new_ip" exit 0 fi
En el cron de root configuro:
*/5 * * * * /etc/iptables_update.sh
No he redirigido la salida estándar, porque tengo configurado “ssmtp” para que el servidor me avise de ciertos eventos. En este caso me avisaría cada vez que la IP ha cambiado. (Sí…me produce satisfacción conocer cuando me cambia la IP; qué le vamos a hacer) Y menos mal, por que gracias a esto pude darme cuenta rápidamente de los fallos en la resolución del host como comentaba antes.
Habilitar Backups
Muy recomendable activar el servicio de Backups de tu proveedor VPS. En este caso Hetzner nos ofrece copias diarias hasta un máximo de 7, por un 20% de lo que cuesta el plan al mes.
De esta manera si algo saliera mal, o tuviéramos una intrusión en el sistema sería fácil volver atrás en el tiempo y recuperar la máquina.
Monitorizar
Y lo más importante, tanto para poder tomar decisiones como para vigilar posibles amenazas, monitorizar el sistema. En este caso utilizo Checkmk desde la Raspberry.
Conclusión
Con unos sencillos pasos hemos reforzado considerablemente la seguridad de nuestro VPS. En estos momentos podríamos crear un «snapshot» para que las futuras máquinas que lancemos puedan estar basadas en esta.
Fuentes:
- http://manpages.ubuntu.com/manpages/focal/
- https://unix.stackexchange.com/questions/91701/ufw–allow-traffic-only-from-a-domain-with-dynamic-ip-address
- https://guidocutipa.blog.bo/proteger-servidor-apache-fail2ban/
- https://community.hetzner.com/tutorials/securing-ssh
- https://www.zonasystem.com/2019/07/fail2ban-idps-control-de-ataques-de-fuerza-bruta-en-servicios-sistemas-linux.html
El post es súper útil y completo!!! Guaoooooo.