8 de mayo de 2015

Pasos para asegurar nuestro VPS

Este tutorial muestra cómo preparar y asegurar un Servidor Privado Virtual (Virtual Private Server, o VPS) con Debian GNU/Linux. Antes de comenzar, se asumen ciertas cosas:

  1. Se posee un nivel intermedio de familiaridad con GNU/Linux.
  2. Se posee un VPS para uso personal al cual tenemos acceso via SSH.
  3. El VPS tiene la ipv4 externa dedicada 250.250.250.155 y nuestro proveedor es propietario del bloque 250.250.0.0/16.(1)
  4. En nuestro VPS solamente tendremos habilitados para el acceso desde el exterior los servicios http, https y ssh.
  5. No se habilitará DNS externo puesto que usualmente se hace en el panel de nuestro proveedor.(2)
  6. Se trabajará como superusuario.

Instalación

Como primer paso, actualicemos el servidor e instalemos algunos paquetes que necesitaremos:

# aptitude update & aptitude safe-upgrade
# aptitude -RvW install dropbear gesftpserver sslh iptables-persistent ulogd fail2ban nginx-light apache2-utils dnsutils telnet ghostscript poppler-utils zip unzip unrar-free p7zip-full less multitail tee mc

Configuración

Ahora vamos a crear un usuario de trabajo. Trabajar como root en un servidor es inseguro, de modo que primeramente crearemos un usuario especial:

adduser operador
usermod -aG sudo operador

El primer comando crea el usuario operador, el segundo lo agrega al grupo sudo, lo cual permitirá ejecutar aplicaciones como superusuario.

Ajustar los permisos para superusuarios

Como para trabajar regularmente utilizaremos el usuario operador anteriormente creado, necesitamos ajustar las opciones de ejecución de comandos como superusuario, para lo cual ejecutamos el siguiente comando:

visudo

Este comando básicamente permite modificar el archivo /etc/sudoers; en el cual deberíamos contener estas líneas:

Defaults env_reset,timestamp_timeout=0
%sudo ALL=(ALL:ALL) ALL

En la primera línea se agrega a los valores predeterminados la opción timestamp_timeout que permite establecer el tiempo de expiración (en minutos) de la contraseña cuando se ejecuta el comando sudo. El valor predeterminado es 5, pero en ocasiones esto es inseguro, por dos motivos:

  1. Si en un descuido abandonamos nuestro equipo con la sesión abierta antes que la contraseña expire, alguien podría ejecutar un comando como superusuario sin restricción alguna.
  2. Si por desconocimiento ejecutamos una aplicación o script que contiene código malicioso antes de que la contraseña expire, la aplicación podría tener acceso a nuestro sistema como superusuario, sin nuestro consentimiento explícito.

De manera que para evitar riesgos, hemos establecido el valor en cero, es decir, cada vez que se ejecute el comando sudo habrá que introducir la contraseña. Si se establece un valor negativo como -1 el efecto es que la contraseña no expire nunca, lo cual produciría el resultado opuesto al que deseamos.

En la segunda línea se aclara que el grupo sudo puede ejecutar cualquier comando en cualquier equipo, que es lo habitual, aunque puede ajustarse.(3) Hay quienes por comodidad ponen la línea de la siguiente manera para evitar tener que teclear la contraseña:

%sudo ALL=(ALL:ALL) NOPASSWD:ALL

No obstante, como explicamos antes esto es riesgoso, y por ello no es recomendable.

Deshabilitar reinicio

Por una cuestión de seguridad, también deshabilitaremos el reinicio utilizando la combinación de teclas Ctrl+Alt+Del, para lo cual debemos agregar esta línea en el archivo /etc/inittab:

ca:12345:ctrlaltdel:/bin/echo "Ctrl+Alt+Del se ha deshabilitado."

Reemplazar OpenSSH con DropBear

La mayoría de los VPS viene con OpenSSH instalado, lo cual sin dudas es muy útil, pero a menos que necesitemos explotar toda la funcionalidad de OpenSSH, hay alternativas más livianas para un VPS, como Dropbear, que suele ser suficiente para el uso habitual. No obstante, un inconveniente de esta aplicación es que no viene con un servidor SFTP integrado, y es por esto que al principio instalamos el paquete gesftpserver.

Para configurar Dropbear, modificaremos el archivo /etc/default/dropbear para que contenga estas dos líneas:

NO_START=0
DROPBEAR_EXTRA_ARGS="-w -p 127.0.0.1:22 -I 1200 -m"

La primera línea simplemente habilita el servicio, y la segunda hace varias cosas:

  1. Evita el acceso como root.
  2. Pone a escuchar el servicio en el puerto 22 de la interfaz local (más adelante explicaremos por qué).
  3. Establece el tiempo de espera (20 minutos).

SSLH

El puerto 22 (SSH) es bien conocido y generalmente es uno de los primeros que los hackers intentan vulnerar, de modo que en su lugar utilizaremos el puerto 443 (SSL). Sucede que ese puerto se utiliza para la navegación segura por HTTPS.

Por esto utilizaremos el paquete sslh que no es más que un multiplexador que analiza los paquetes que llegan al puerto 443, y los enruta internamente a un servicio u otro en dependencia de si el tipo de tráfico es SSH o SSL.

SSLH no puede escuchar en una interfaz donde ya esté escuchando otro servicio, y es por esto que anteriormente pusimos a escuchar Dropbear en la interfaz local.

Ahora lo que necesitamos hacer es indicar a sslh la interfaz y el puerto por el cual debe escuchar y hacia dónde redirigir los paquetes en dependencia del tipo de servicio, y para esto modificaremos el archivo de configuración /etc/default/sslh:

DAEMON=/usr/sbin/sslh
DAEMON_OPTS="--user sslh --listen 250.250.250.155:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:443 --pidfile /var/run/sslh/sslh.pid"
RUN=yes

Finalmente, reiniciamos los servicios:

service ssh stop && service dropbear start && service sslh restart

Tras el comando anterior probablemente se interrumpa nuestra sesión segura, en cuyo caso basta con volver a iniciar sesión, pero esta vez con el usuario de trabajo y usando el puerto 443. Si la sesión no se interrumpe, de cualquier manera conviene cerrarla e iniciar nuevamente con los valores apropiados.

Si todo funciona correctamente, podemos seguir trabajando como root y si deseamos, desinstalar OpenSSH:

sudo su -
aptitude -r purge openssh-server

Cortafuegos

Lo próximo que realizaremos es separar los registros del cortafuegos hacia el archivo independiente /var/log/firewall.log para facilitar su posterior análisis, que es el motivo por el cual instalamos el paquete ulogd al inicio. Para esto editaremos el archivo /etc/logd.conf para ajustar la sección relevante:

[LOGEMU]
file="/var/log/firewall.log"
sync=1

A continuación, modificaremos el archivo de rotación de registros /etc/logrotate/ulogd para mantener una rotación diaria (con fecha) y guardar las salvas comprimidas en el directorio /var/log/ulog/:

/var/log/ulog/*.log /var/log/firewall.log {
daily
dateext
missingok
compress
delaycompress
sharedscripts
create 640 root adm
postrotate
/etc/init.d/ulogd reload
mv /var/log/firewall.log-*.gz /var/log/ulog/
endscript
}

Entonces crearemos las reglas de netfilter ejecutando lo siguiente:

IPT=$(which iptables)
IPEXT=250.250.250.155
IPEXTBLK=250.250.0.0/16
IPBCAST=255.255.255.255
$IPT -F
$IPT -X
$IPT -Z
$IPT -A INPUT -i lo -j ACCEPT
$IPT -P INPUT DROP
$IPT -P FORWARD DROP
$IPT -P OUTPUT ACCEPT
$IPT -A INPUT -m state --state INVALID -j ULOG --ulog-prefix IN_INVALID
$IPT -A INPUT -p igmp -j ULOG --ulog-prefix IN_IGMP
$IPT -A INPUT -m pkttype --pkt-type broadcast -j ULOG --ulog-prefix IN_BCAST
$IPT -A INPUT -m pkttype --pkt-type multicast -j ULOG --ulog-prefix IN_MCAST
$IPT -A FORWARD -j ULOG --ulog-prefix FORWARD
$IPT -N ICMP_IN
$IPT -A INPUT ! -i lo -p icmp -j ICMP_IN
$IPT -A ICMP_IN -p icmp -f -j ULOG --ulog-prefix IN_ICMP_FRAGMENTED
$IPT -A ICMP_IN -p icmp -m icmp -m length ! --length 28:1322 -j ULOG --ulog-prefix IN_ICMP_INVALIDSIZE
$IPT -A ICMP_IN -p icmp -m icmp -m hashlimit --hashlimit-above 4/sec --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-name icmpflood -j ULOG --ulog-prefix IN_ICMP_FLOOD
$IPT -A ICMP_IN -p icmp -m icmp -m hashlimit --hashlimit-upto 64kb/min --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-name icmpattack -j ULOG --ulog-prefix IN_ICMP_FLOOD
$IPT -A ICMP_IN -p icmp -m icmp -m u32 ! --u32 "0x4&0x3fff=0x0" -j ULOG --ulog-prefix IN_ICMP_ATTACK
$IPT -A ICMP_IN -p icmp -m icmp ! --icmp-type echo-request -m state --state NEW -j ULOG --ulog-prefix IN_ICMP_INVALID
$IPT -A ICMP_IN -p icmp -m icmp --icmp-type echo-request -j ULOG --ulog-prefix IN_ICMP
$IPT -A ICMP_IN -p icmp -m icmp --icmp-type echo-request -m limit --limit 1/sec --limit-burst 4 -j ACCEPT
$IPT -A ICMP_IN -p icmp -m icmp --icmp-type echo-reply -m limit --limit 2/sec --limit-burst 4 -j ACCEPT
$IPT -A ICMP_IN -p icmp -m icmp --icmp-type destination-unreachable -m limit --limit 2/sec --limit-burst 4 -j ACCEPT
$IPT -A ICMP_IN -p icmp -m icmp --icmp-type time-exceeded -m limit --limit 2/sec --limit-burst 4 -j ACCEPT
$IPT -A ICMP_IN -p icmp -m icmp --icmp-type parameter-problem -m limit --limit 2/sec --limit-burst 4 -j ACCEPT
$IPT -A ICMP_IN -j RETURN
$IPT -N UDP_IN
$IPT -A INPUT ! -i lo -p udp -j UDP_IN
$IPT -A UDP_IN ! -i lo ! -p udp -f -j ULOG --ulog-prefix IN_UDP_FRAGMENTED
$IPT -A UDP_IN -p udp -m udp --sport 53 -m length ! --length 28:576 -j ULOG --ulog-prefix IN_UDP_DNS_INVALIDSIZE
$IPT -A UDP_IN -p udp -m udp --dport 53 -m -state --state NEW -j ULOG --ulog-prefix IN_UDP_DNSREQUEST
$IPT -A UDP_IN -p udp -m udp --dport 53 -m -state --state NEW -j REJECT --reject-with icmp-port-unreachable
$IPT -A UDP_IN -p udp -m udp ! --sport 53 ! -s $IPEXTBLK ! -d $IPBCAST -m state --state NEW -j ULOG --ulog-prefix IN_UDP
$IPT -A UDP_IN -p udp -m udp -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPT -A UDP_IN -j RETURN
$IPT -N TCP_IN
$IPT -A INPUT ! -i lo -p tcp -j TCP_IN
$IPT -A TCP_IN ! -i lo ! -p tcp -f -j ULOG --ulog-prefix IN_TCP_FRAGMENTED
$IPT -A TCP_IN -p tcp -m tcp --sport 53 -m state --state ESTABLISHED,RELATED -m length ! --length 513:1500 -j ULOG --ulog-prefix IN_TCP_DNS_INVALIDSIZE
$IPT -A TCP_IN -p tcp -m tcp --dport 53 -m state --state NEW -j ULOG --ulog-prefix IN_TCP_DNS
$IPT -A TCP_IN -p tcp -m tcp --dport 53 -m state --state NEW -j REJECT --reject-with icmp-port-unreachable
$IPT -A TCP_IN -p tcp -m tcp -m multiport ! --dports 80,443 -m state --state NEW -j ULOG --ulog-prefix IN_TCP
$IPT -A TCP_IN -p tcp -m tcp -m multiport --dports 80,443 -m state --state NEW -m hashlimit --hashlimit-upto 4/seg --hashlimit-burst 16 --hashlimit-mode srcip --hashlimit-name navreq -j ACCEPT
$IPT -A TCP_IN -p tcp -m tcp -m multiport --dports 80,443 -m state --state ESTABLISHED -m connlimit ! --connlimit-above 16 -j ACCEPT
$IPT -A TCP_IN -p tcp -m tcp -m multiport ! --dports 80,443 -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPT -A TCP_IN -j RETURN
unset IPEXT
unset IPEXTBLK
unset IPBCAST
unset IPT

Con la configuración anterior, nuestro VPS debería quedar razonablemente asegurado, pero si deseamos podemos asegurarlo un poco más, para lo cual podemos usar algunas reglas más avanzadas.

No todos los VPS permiten la instalación de módulos extra para netfilter, pero uno muy útil es psd, que permite evitar los escaneos de puertos. Lamentablemente este módulo no viene integrado en netfilter por defecto, por lo que es necesario instalar ciertos paquetes y luego construir el módulo:

aptitude -RvW install iptables-dev xtables-addons-source module-assistant
module-assistant --verbose --text-mode auto-install xtables-addons-source

Una vez hecho lo anterior, podemos agregar una regla como esta:

iptables -A INPUT -m psd --psd-weight-threshold 15 --psd-delay-threshold 20 --psd-lo-port-weight 3 --psd-hi-ports-weight 1 -j ULOG --ulog-prefix IN_PORTSCAN

La regla anterior básicamente significa que crearemos un contador que se incrementará en 3 cada vez que se intente acceder a un puerto inferior al 1024 y en 1 cada vez que se intente acceder a un puerto superior al 1023, y cuando este contador llegue a 21 en un lapso inferior a 30 segundos, se registrarán los paquetes mediante ulog como un intento de portscan. Igual podrían descartarse los paquetes de una vez, pero en este caso pretendemos utilizar fail2ban, que configuraremos más adelante.

Una vez creadas las reglas, debemos tomar ciertas precauciones para hacerlas persistentes, pues en caso contrario las perderemos al reiniciar el servidor. Hay varias maneras de lograr esto; en este tutorial utilizaremos el paquete iptables-persistent que instalamos al principio, que almacena las reglas en /etc/iptables/rules.v4 y /etc/iptables/rules.v6 para ipv6.

iptables-save > /etc/iptables/rules.v4

De hecho, aunque el uso de ipv6 en Cuba aun no está extendido, bien podríamos crear unas reglas básicas:

IPT=$(which ip6tables)
$IPT -P INPUD DROP
$IPT -P FORWARD DROP
$IPT -P OUTPUT ACCEPT
$IPT -A INPUT -i lo -j ACCEPT
$IPT -A INPUT ! -i lo -m state --state ESTABLISHED,RELATED -j ACCEPT
unset IPT

Estas reglas también pueden hacerse persistentes:

ip6tables-save > /etc/iptables/rules.v6

Finalmente para mayor seguridad, limpiamos el registro del cortafuegos y reiniciamos los servicios:

echo -n > /var/log/firewall.log
service logrotate restart
service ulogd restart
service iptables-persistent restart

Nginx

Utilizaremos Nginx como servidor web, porque los VPS suelen tener una cantidad de memoria RAM reducida en comparación con un servidor real, de modo que generalmente conviene tener algo más liviano que Apache.

Antes de configurar Nginx, crearemos un certificado (sin contraseña) para su uso por HTTPS:

cd /etc/nginx
openssl genrsa -des3 -out cert.key 4096
cp -v cert.key cert.key.original
openssl req -new -key cert.key -out cert.csr
openssl rsa -in cert.key.original -out cert.key
openssl x509 -req -days 365 -in cert.csr -signkey cert.key -out cert.crt

Una vez realizado esto, crearemos un archivo de contraseñas para el usuario “elusuario”:

htpasswd -c .htpasswd elusuario

A continuación, modificaremos el archivo /etc/nginx/sites-available/default para ajustar las preferencias del sitio por defecto. Podría quedar aproximadamente así:

server {
server_name localhost;

root /var/www;
index index.html index.htm default.html default.htm;

location / {
auth_basic "Login";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ /index.html;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
}
}

server {
listen 127.0.0.1:443;
server_name localhost;

root html;
index index.html index.htm default.html default.htm;

ssl on;
ssl_certificate cert.crt;
ssl_certificate_key cert.key;

ssl_session_timeout 5m;

ssl_protocols SSLv3 TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
ssl_prefer_server_ciphers on;

location / {
auth_basic "Login";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ =404;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
}
}

Comprobamos que la configuración esté correcta:

nginx -t

Finalmente, reiniciamos el servicio:

service nginx restart

Fail2Ban

Antes de comenzar a configurar Fail2Ban, para mayor seguridad detenemos el servicio y limpiamos el registro:

fail2ban-client stop
echo -n > /var/log/fail2ban.log

A continuación, creamos el archivo de configuración /etc/fail2ban/jail.local con el siguiente contenido personalizado:

# Custom configuration file /etc/fail2ban/jail.local
#
[DEFAULT]
findtime = 43200 ; 12 hours
bantime = 86400 ; 1 day
maxretry = 3 ; ban will take effect after the 4th attempt

[ssh]
enabled = false

[nginx-auth]
enabled = true
filter = nginx-auth
action = iptables-multiport[name=NoAuthFailures, port="http,https"]
logpath = /var/log/nginx*/*error*.log

[nginx-badbots]
enabled = true
filter = apache-badbots
action = iptables-multiport[name=BadBots, port="http,https"]
logpath = /var/log/nginx*/*access*.log
bantime = 604800 ; 1 week
maxretry = 0

[nginx-login]
enabled = true
filter = nginx-login
action = iptables-multiport[name=NoLoginFailures, port="http,https"]
logpath = /var/log/nginx*/*access*.log
bantime = 1800 ; 30 minutes

[nginx-noscript]
enabled = true
action = iptables-multiport[name=NoScript, port="http,https"]
filter = nginx-noscript
logpath = /var/log/nginx*/*access*.log
maxretry = 0

[nginx-proxy]
enabled = true
action = iptables-multiport[name=NoProxy, port="http,https"]
filter = nginx-proxy
logpath = /var/log/nginx*/*access*.log
bantime = 604800 ; 1 week
maxretry = 0

[firewall]
enabled = true
action = iptables-multiport[name=Firewall]
filter = firewall
logpath = /var/log/firewall.log
maxretry = 0

Una vez realizado esto, creamos en el directorio /etc/fail2ban/filters.d/ los siguientes archivos:

# Auth filter /etc/fail2ban/filter.d/nginx-auth.conf
# Blocks IPs that fail to authenticate using basic authentication
#
[Definition]
failregex = no user/password was provided for basic authentication.*client: <HOST>
user .* was not found in.*client: <HOST>
user .* password mismatch.*client: <HOST>
ignoreregex =

# Login filter /etc/fail2ban/filter.d/nginx-login.conf
# Blocks IPs that fail to authenticate using web application's log in page
# Scan access log for HTTP 200 + POST /sessions => failed log in
#
[Definition]
failregex = ^<HOST> -.*POST /sessions HTTP/1\.." 200
ignoreregex =

# Noscript filter /etc/fail2ban/filter.d/nginx-noscript.conf
# Block IPs trying to execute scripts such as .php, .pl, .exe and other funny scripts.
# Matches e.g.
# 192.168.1.1 - - "GET /something.php
#
[Definition]
failregex = ^<HOST> -.*GET .*(\.php|\.asp|\.exe|\.pl|\.cgi|\scgi)
ignoreregex =

# Proxy filter /etc/fail2ban/filter.d/proxy.conf
# Block IPs trying to use server as proxy.
# Matches e.g.
# 192.168.1.1 - - "GET http://ift.tt/ItcThN
#
[Definition]
failregex = ^<HOST> -.*GET http.*
ignoreregex =

# Firewall filter /etc/fail2ban/filter.d/firewall.conf
#
[Definition]
failregex = ^.* IN_(INVALID|PORTSCAN|UDP|TCP|) .* SRC=<HOST> .*$
ignoreregex =

Finalmente, iniciamos el servicio y cargamos la configuración:

fail2ban-service -b
fail2ban-client reload

Comprobación

Como último paso, podemos visualizar los registros con tail -f o multitail –follow-all. De hecho, esta última aplicación ofrece la ventaja de que permite visualizar varios archivos al mismo tiempo y proporciona un resaltado de sintaxis básico.

En caso de que no se configure en el VPS una cuenta de correo, conviente deshabilitar un mensaje de advertencia que sale al iniciar multitail, para lo cual ejecutaremos el siguiente comando:

echo "check_mail:0" > ~/.multitailrc

De hecho bien podríamos hacer un alias(4) para visualizar los registros rápidamente con un comando breve, por ejemplo, “flog”:

alias flog='multitail --follow-all /var/log/firewall.log /var/log/fail2ban.log'

1) Estos son valores ficticios.
2) Habilitar otros servicios es fácil una vez comprendido el funcionamiento.
3) Para más detalles, ejecutar man sudoers.
4) Opcionalmente podría añadirse al archivo ~/.bash_aliases


Fuente: Desde Linux

0 comentarios: