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!
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:
Editamos sudo nano /etc/ssh/sshd_config y le añadimos:
PermitRootLogin no
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)
De default esto está deshabilitado, pero no es mala práctica indicarlo:
PermitEmptyPasswords no
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 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 = sshporport = 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 (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
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
commentes 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
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'
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
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/24por la subred de tu red, y10.8.0.0/24por 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.
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 numbereddespués de cada borrado, o ve de mayor a menor.
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 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-extrasreemplaza el paquetenginxestá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.
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.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.
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
indexy conautoindex 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).
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
404en lugar de403no 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.
Como siempre, antes de recargar verificamos sintaxis:
sudo nginx -t
Si todo está OK:
sudo systemctl reload nginx
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.
Proxy se mapea a la variable HTTP_PROXY, usada por muchas librerías para configurar proxies de salida.192.168.1.0/24 incluye desde 192.168.1.0 hasta 192.168.1.255.