Apuntes de sistema: Servidor Linux, asegurando la fortaleza (parte 4)

linux ubuntu server seguridad ssh ufw fail2ban nginx apuntes de sistema

En las partes 1, 2 y 3 ya armamos un Ubuntu Server con NGINX y PHP funcionando. Si quieres exponer tu servidor a internet (con port forwarding, un VPS, o lo que sea), pasa a ser un blanco mas que jugoso para bots que escanean direcciones IP buscando puertos abiertos, contraseñas débiles, y configuraciones por defecto.

No es paranoia: pon un servidor SSH abierto en internet con clave débil, y en menos de una hora vas a tener cientos de intentos de login automatizados. Te lo firmo.

So, en esta entrega vamos a apretar tornillos: cerraremos SSH, instalaremos un Fail2Ban (que banea IPs que intentan reventar el login), configuraremos el firewall UFW, y dejaremos NGINX con algunos headers y reglas para que no exponga mas información de la cuenta.

Conectemonos por SSH al servidor y empecemos!

Índice

SSH: el primer candado

SSH es la puerta principal del servidor. Si la dejas mal cerrada, todo lo demás da igual. Lo mínimo que hay que hacer:

1. Deshabilitar el login como root

Editamos sudo nano /etc/ssh/sshd_config y le añadimos:

PermitRootLogin no

2. Limitar acceso a ciertos usuarios

Es posible limitar el acceso SSH usando AllowUsers. Añadimos la linea:

AllowUsers juan usuario2

Agrega los que estimes (en la prueba usamos juan, de ejemplo)

3. Deshabilitar login vacío

De default esto está deshabilitado, pero no es mala práctica indicarlo:

PermitEmptyPasswords no

4. (Opcional) Cambiar el puerto

No es seguridad real (un escaneo de puertos lo encuentra igual), pero reduce muchísimo el ruido de los bots automatizados que solo prueban el puerto 22. Si quieres, en el mismo archivo, descomenta la línea y cambias el puerto, por el que estimes conveniente:

Port 2222

Si cambias el puerto, recuerda que después tendrás que conectarte con ssh -p 2222 user@ip. Y abrirlo en UFW (lo veremos en un rato).

Guardamos (Ctrl+X, Y, Enter) y recargamos el servicio:

sudo systemctl reload ssh

Ojo al charqui: NO cierres tu sesión SSH actual hasta probar que puedes abrir una nueva sesión con los cambios (en especial si cambiaste el puerto de entrada). Si te equivocaste en algo y reiniciaste el servicio, podrías quedarte fuera, y tener que ajustar esto en la maquina en local. Abre otra terminal y prueba conectarte; si funciona, recién entonces cierra la primera.


Fail2Ban: el portero del SSH (y mas adelante de otros servicios)

Fail2Ban es un servicio que, al detectar múltiples intentos de login fallidos desde una misma IP, la bloquea automáticamente. Simple y efectivo.

Instalamos:

sudo apt-get install fail2ban -y

La configuración por defecto está en /etc/fail2ban/jail.conf, pero no vamos a editar ese archivo (ya que el mismo servicio indica que este puede ser modificado mas adelante por una actualización). En su lugar, creamos uno propio:

sudo nano /etc/fail2ban/jail.local

Y le pegamos esto:

[sshd]
port    = ssh
bantime  = 3600
maxretry = 5
ignoreip = 127.0.0.1/8 192.168.1.0/24

Qué hace cada cosa:

  • bantime: cuánto dura el baneo (aquí, 1 hora en segundos).
  • maxretry: cuántos intentos fallidos antes de banear.
  • ignoreip: IPs que nunca se banean. Pon aquí tu red local para no auto-banearte.

Si cambiaste el puerto SSH antes, reemplaza port = ssh por port = 2222 (o el que hayas elegido).

Habilitamos y arrancamos el servicio:

sudo systemctl enable fail2ban && sudo systemctl start fail2ban

Para ver el estado del servicio (y en especial como está trabajando con ssh), lo podemos ver con el siguiente comando:

sudo fail2ban-client status sshd

Si necesitas banear una IP en especifico, puedes usar:

sudo fail2ban-client set sshd banip 123.456.789.0

(Cambia por su puesto la ip a la que corresponda). Y a la inversa, para desbanear una IP (te baneaste sin querer, o quieres dar acceso):

sudo fail2ban-client set sshd unbanip 123.456.789.0

Un dato, Fail2Ban sirve para mas servicios. Si, como Wordpress, SMB, etc. Venga, que hay muchas formas de asegurar el servidor. Y después dicen que Linux no es seguro.


UFW: el firewall amigable

UFW (Uncomplicated Firewall) es un cortafuegos sencillo para Linux. Permite bloquear o permitir tráfico de red con comandos claros y fáciles de recordar, sin complicaciones.

Si seguiste la parte 2 probablemente ya lo instalaste. Si no:

sudo apt-get install ufw -y

Política por defecto

Antes de habilitarlo, definamos la regla básica: bloquear todo lo que entra, permitir todo lo que sale.

sudo ufw default deny incoming
sudo ufw default allow outgoing

Ojo: NO actives UFW todavía. Si lo activas ahora, te va a echar de tu sesión SSH al instante. Primero abrimos el puerto SSH:

sudo ufw allow ssh

O si cambiaste el puerto:

sudo ufw allow 2222/tcp comment 'SSH puerto custom'

El parámetro comment es muy útil para acordarte de qué hace cada regla. Aprovéchalo, después con muchas reglas vas a agradecerlo.

Ahora si, habilitamos ufw:

sudo ufw enable

Abriendo puertos para servicios

Si tenemos NGINX corriendo (de la parte 3), tenemos que abrir el puerto 80 (HTTP) y, si tienes SSL, el 443 (HTTPS):

sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

Permitir o bloquear por protocolo

Si quieres abrir un puerto solo en TCP, UDP, o ambos:

sudo ufw allow 53/udp comment 'DNS'
sudo ufw allow 5060/tcp comment 'SIP'
sudo ufw allow 1194 comment 'OpenVPN' # sin /tcp ni /udp, abre ambos

Restringir SSH solo a la red local o VPN

Esta es muy útil. Si solo te conectas por SSH desde casa, o desde tu VPN, puedes bloquear el SSH para el resto del mundo:

sudo ufw allow from 192.168.1.0/24 to any port 22 comment 'SSH red local'
sudo ufw allow from 10.8.0.0/24 to any port 22 comment 'SSH VPN'
sudo ufw delete allow ssh # Borramos la regla genérica

Reemplaza 192.168.1.0/24 por la subred de tu red, y 10.8.0.0/24 por la de tu VPN si tienes una. Si solo quieres permitir SSH desde una IP específica: sudo ufw allow from 192.168.1.50 to any port 22.

Ver y borrar reglas

Para ver las reglas activas:

sudo ufw status verbose

Y, mejor aún, para verlas numeradas (necesario para borrarlas después):

sudo ufw status numbered

Te aparecerá una lista del tipo:

[ 1] 22/tcp                     ALLOW IN    Anywhere
[ 2] 80/tcp                     ALLOW IN    Anywhere
[ 3] 443/tcp                    ALLOW IN    Anywhere

Para borrar la regla número 2:

sudo ufw delete 2

Importante: Cada vez que borras una regla, los números se reordenan. Si vas a borrar varias, vuelve a ejecutar sudo ufw status numbered después de cada borrado, o ve de mayor a menor.

Desactivar el logging

UFW por defecto registra todo el tráfico bloqueado en /var/log/ufw.log. Esto puede llenar el log rápidamente, especialmente si estas expuesto a internet. ¿Quieres desactivarlo? Es facil:

sudo ufw logging off

Si quieres mantenerlo pero menos verboso, también se puede: sudo ufw logging low.

Los niveles son off, low, medium, high, full.

Un tip, si es que está configurado IPv6, UFW va a hacer reglas para IPv4 y IPv6. Si quieres trabajar sólamente con IPv4, debes editar un archivo de configuración, sudo nano /etc/default/ufw, en la línea que indica IPV6=yes, debes cambiarla a IPV6=no.

Y otro, si es que estás trabajando con Docker, este puede saltarse UFW (Docker maneja sus reglas directamente en iptables) Se puede evitar, pero eso es otro tema.


NGINX: reglas básicas de seguridad

NGINX por defecto ya viene razonablemente seguro, pero hay algunos detalles que vale la pena tocar. Editamos la configuración:

sudo nano /etc/nginx/nginx.conf

Dentro del bloque http { ... }, asegúrate de que esté esta línea (suele venir comentada):

server_tokens off;

Esto evita que NGINX muestre su versión en los headers HTTP y en las páginas de error. Es un detalle, pero un atacante que sabe la versión exacta puede buscar vulnerabilidades conocidas para esa versión.

Con server_tokens off ocultamos la versión, pero el header Server: nginx sigue ahí, delatando qué servidor web estamos usando. Para esconderlo (o cambiarlo por lo que se nos antoje), NGINX por defecto no trae la directiva, hay que instalar un módulo extra: nginx-extras:

sudo apt-get install nginx-extras -y

Ojo: nginx-extras reemplaza el paquete nginx estándar por una versión con módulos adicionales. El servicio sigue funcionando igual, solo cambia el binario por debajo.

Una vez instalado, dentro del mismo bloque http { ... } añadimos:

more_set_headers "Server: webserver";

El valor (despues de Server:)puede ser lo que quieras: webserver, apache (para despistar), un string vacío para no mostrar nada, o el chiste que se te ocurra. Después, como siempre, verificamos la configuración y recargamos:

sudo nginx -t && sudo systemctl reload nginx

Donde antes salía Server: nginx/1.x.x, ahora saldrá lo que hayas puesto en more_set_headers.

Esto es seguridad por oscuridad: no hace tu servidor invulnerable, solo le pone un poco más difícil al curioso de turno identificar qué estás corriendo. La seguridad real viene de tener todo actualizado, firewall configurado y permisos bien puestos. Pero ocultar el cartel "soy NGINX 1.24" no le hace mal a nadie.

Headers de seguridad

Estos headers le dicen al navegador cómo comportarse al cargar el sitio. Edita el archivo del sitio (en la parte 3 trabajamos con /etc/nginx/sites-available/default):

sudo nano /etc/nginx/sites-available/default

Dentro del bloque server { ... }, añade:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Que hace cada uno:

  • X-Frame-Options: evita que tu sitio sea cargado dentro de un <iframe> en otro sitio (protege contra clickjacking).
  • X-Content-Type-Options: evita que el navegador "adivine" el tipo MIME de un archivo. Previene algunos ataques de inyección.
  • Referrer-Policy: controla qué información de referencia (de qué página viene el usuario) se envía a otros sitios.

Autoindex off

Si NGINX no encuentra un index.html o index.php en una carpeta, por defecto NO lista los archivos (que sería un riesgo de seguridad). Pero hay configuraciones donde esto se activa, por error o intencionalmente. Para ser explícitos:

autoindex off;

Lo añades dentro del bloque server o location.

Custom 404 y 403

Por defecto, NGINX muestra una página bien sosa cuando algo falla: un fondo blanco, "404 Not Found" y poco más. Funcional, pero feo (y además expone que es NGINX). Podemos reemplazarla por algo propio.

Primero, creamos una página de error dentro de la carpeta web (puedes usar HTML simple, o algo más elaborado si quieres):

sudo nano /var/www/html/error.html

Un ejemplo minimalista:

<!DOCTYPE html>
<html lang="es">
<head><meta charset="UTF-8"><title>Error</title></head>
<body style="font-family:sans-serif;text-align:center;padding:50px">
    <h1>Nada por aquí</h1>
    <p>Esto no está por aquí. ¿Seguro que escribiste bien la URL?</p>
    <a href="/">Volver al inicio</a>
</body>
</html>

Guardamos (Ctrl+O, Ctrl+X).

El 403 aparece cuando el archivo existe pero no tienes permiso para verlo (típico al intentar acceder a una carpeta sin index y con autoindex off). El 404 es cuando derechamente el archivo no existe.

Ahora le decimos a NGINX que use esta página. Editamos la configuración del sitio:

sudo nano /etc/nginx/sites-available/default

Dentro del bloque server { ... }, añadimos:

error_page 404 /error.html;
error_page 403 /error.html;

En este ejemplo, hicimos que la misma página sea para los errores 404 (no encontrado), como los 403 (no autorizado).

Para probar, intenta acceder a una URL inexistente, como http://[IP-del-servidor]/estonoexiste. Deberías ver tu nueva página de error.

Minidato: Si tienes el sitio en PHP (como un CMS), normalmente el propio sistema maneja sus errores 404/403 internamente y NGINX no llega a mostrar el suyo. Esta configuración aplica sobre todo a sitios estáticos o a errores que ocurren antes de que PHP entre en juego (rutas de carpetas restringidas, archivos físicos faltantes, etc).

Bloquear acceso a archivos sensibles

Hay archivos que no deberían ser accesibles desde el navegador, pero que muchas veces quedan expuestos: archivos de configuración (.env), repositorios git (.git), etc.

Dentro del bloque server, añade:

location ~ /\.(env|git|svn|htaccess|htpasswd) {
    deny all;
    return 404;
}

Devolver 404 en lugar de 403 no es estrictamente necesario, pero es preferible: no le dice al atacante "este archivo existe pero no puedes verlo", si no "este archivo no existe". Menos información, mejor.

Probar y recargar

Como siempre, antes de recargar verificamos sintaxis:

sudo nginx -t

Si todo está OK:

sudo systemctl reload nginx

Bloquear el header Proxy (httpoxy)

En 2016 se reveló httpoxy (CVE-2016-5385 y familia), una vulnerabilidad de las antiguas: en entornos CGI/FastCGI, si un atacante manda un header Proxy: en su petición HTTP, este llega a PHP como la variable de entorno HTTP_PROXY. Y resulta que muchas librerías HTTP (como Guzzle) usan justamente esa variable para configurar un proxy de salida. ¿El resultado? El atacante puede redirigir todas las peticiones que tu servidor haga hacia afuera, capturando credenciales, tokens, lo que sea.

Las versiones modernas de PHP y la mayoría de librerías ya mitigan esto por su lado, pero añadir el blindaje a nivel NGINX no cuesta nada y es defensa en profundidad. Con dos cosas a agregar basta:

echo 'fastcgi_param  HTTP_PROXY         "";' | sudo tee -a /etc/nginx/fastcgi_params
echo 'fastcgi_param  HTTP_PROXY         "";' | sudo tee -a /etc/nginx/fastcgi.conf

Esto fuerza el HTTP_PROXY que recibe PHP a string vacío, sin importar lo que mande el cliente.


Y con esto ya tenemos los basics de seguridad cubiertos: SSH cerrado, Fail2Ban vigilando los intentos de login, UFW filtrando el tráfico y NGINX sin filtrar información sensible. No es seguridad nivel Pentagono, pero te deja en una posición mucho mas digna que la mayoría de servidores que andan dando vueltas por internet.


Glosario

  • Fail2Ban: Servicio que monitorea logs y banea automáticamente IPs con comportamiento sospechoso (muchos logins fallidos, por ejemplo).
  • httpoxy: Vulnerabilidad de 2016 que afecta a aplicaciones en CGI/FastCGI. Aprovecha que el header HTTP Proxy se mapea a la variable HTTP_PROXY, usada por muchas librerías para configurar proxies de salida.
  • iptables: Herramienta nativa de Linux para configurar reglas de firewall a bajo nivel. UFW es una capa amigable encima de iptables.
  • MIME (Multipurpose Internet Mail Extensions): Estándar que identifica el tipo de contenido de un archivo (texto, imagen, etc).
  • Port forwarding: Redirección de un puerto del router hacia un dispositivo dentro de la red local, exponiéndolo a internet.
  • Subred (subnet): Rango de direcciones IP dentro de una red. Por ejemplo, 192.168.1.0/24 incluye desde 192.168.1.0 hasta 192.168.1.255.
  • UFW (Uncomplicated Firewall): Herramienta simplificada para configurar el firewall de Linux.