Asegurando Servidor Web I iptables
Éste será el primero de una serie de articulos relacionados con la seguridad en un servidor web. Pese a que está enfocado en un servidor web, se puede aplicar a otros servicios cambiando los puertos correspondientes.
El primer paso para securizar el servidor web, será implementar un firewall, para lo que utilizaremos el iptables.
A la hora de implementar un firewall, lo mas seguro es cortarlo todo y después permitir solo aquello que sea absolutamente necesario.
Con el fin de hacer nuestro script lo mas genérico posible, utilizaremos algunas variables al principio para especificar las ips que aparecerán:
IPWEB: la ip del servidor web.
IPWAN: la ip pública del servidor (si el servidor cuenta con mas de una ip, es interesante separar y correr el servicio solo en aquella que se ha especificado).
IPADMIN: ip desde la que se administra el servidor.
IPWEBMASTER: ip del webmaster.
IPNTP: Servidor de tiempo.
Nuestro script para manejar iptables, que llamaremos fw.sh, comenzará con las siguientes reglas:
#!/bin/bash #HOSTS IPWEB=192.168.1.100 IPWAN=192.168.1.100 IPADMIN=192.168.1.33 IPWEBMASTER=192.168.1.34 IPNTP=192.168.168.1 #Borramos antiguas reglas: iptables -F #Politíca por defecto: iptables -P INPUT DROP iptables -P OUTPUT DROP iptables -P FORWARD DROP
Dándole permisos de ejecución al script y ejecutandolo tendremos un firewall bastante seguro, aunque poco práctico.
En este punto habréis notado, si teníais cualquier conexión con el servidor (incluso un ssh), que os ha tirado. Cuando configuramos reglas en el iptables, esta posibilidad siempre hay que contemplarla. Si estamos administrando un sistema remoto, nos quedaríamos fuera sin posibilidad de volver a entrar, a no ser que se reinicie o alguien nos quite dichas reglas.
Para evitar esto, podemos programar un reinicio automático de unos 5 minutos antes de ejecutar el script. Si nada ocurreo, lo cancelamos, si pasa algo, esperamos a que se reinicie y lo corregimos.
shutdown -r 5 &
./fw.sh
shutdown -c
Tenemos corriendo un servidor web, por lo que nos gustaría que éste sea accesible. Añadiremos una regla para permitir la entrada a las conexiones nuevas que vayan dirigidas a la ip asignada al servicio:
#Permitimos la entrada por el puerto 80 TCP: iptables -A INPUT -p TCP --dport 80 -d $IPWEB -m state --state NEW -j ACCEPT
Cada vez que llega un paquete a nuestro servidor, este recorre todas las reglas del firewall hasta que coincide con una. En ese caso la ejecuta y deja de mirar el resto. Si un paquete de una conexión ha coincidido con una regla y ha conseguido establecer una comunicación, podemos considerar fiables el resto de paquetes de esa misma conexión. Añadiremos una regla al principio, que permita pasar los paquetes de conexiones ya establecidas o relacionados. Con ésto evitamos que tengan que recorrer de nuevo todas las reglas del iptables hasta coincidir con la suya, disminuyendo así la carga en el servidor:
#Permitimos paquetes de conexiones previamente establecidas: iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
Con esas simples reglas, nuestro servidor ya está protegido de todos ataques que no vayan al puerto 80.
Seguramente, queramos permitir el acceso por ssh. Creo que es muy importante no permitir el acceso a todo el mundo, sino a las ips desde las que se va a administrar el servidor. De hecho, tras permitir el acceso a estas ips, cortaremos el acceso a las que quieran acceder a dicho puerto inmediatamente. No esperaremos a que se cumpla la política por defecto (Drop en nuestro caso). Estos intentos también los registraremos para posterior análisis:
#Permitimos el acceso por ssh a nuestra ip y cortamos al resto registrandolo: iptables -A INPUT -p TCP --dport 22 -s $IPADMIN -d $IPWAN -m state --state NEW -j ACCEPT iptables -A INPUT -p TCP --dport 22 -j LOG --log-prefix "Intento_SSH: " iptables -A INPUT -p TCP --dport 22 -j DROP
Si se conecta desde IPADMIN a la ip IPWAN, el acceso es permitido, pero desde cualquier otro sitio no se acepta y además se registra. Podemos ver estos intentos de conexión en el syslog:
grep Intento_SSH /var/log/syslog
Jun 6 02:41:27 xen01 kernel: Intento_SSH: IN=eth0 OUT= MAC=00:16:3e:59:02:bf:00:40:f6:2c:53:78:08:00 SRC=192.168.1.34 DST=192.168.1.100 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=51588 DF PROTO=TCP SPT=51472 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0 Jun 6 02:41:30 xen01 kernel: Intento_SSH: IN=eth0 OUT= MAC=00:16:3e:59:02:bf:00:40:f6:2c:53:78:08:00 SRC=192.168.1.34 DST=192.168.1.100 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=51589 DF PROTO=TCP SPT=51472 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0 Jun 6 02:41:36 xen01 kernel: Intento_SSH: IN=eth0 OUT= MAC=00:16:3e:59:02:bf:00:40:f6:2c:53:78:08:00 SRC=192.168.1.34 DST=192.168.1.100 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=51590 DF PROTO=TCP SPT=51472 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0 Jun 6 02:41:48 xen01 kernel: Intento_SSH: IN=eth0 OUT= MAC=00:16:3e:59:02:bf:00:40:f6:2c:53:78:08:00 SRC=192.168.1.34 DST=192.168.1.100 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=51591 DF PROTO=TCP SPT=51472 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0 Jun 6 02:42:12 xen01 kernel: Intento_SSH: IN=eth0 OUT= MAC=00:16:3e:59:02:bf:00:40:f6:2c:53:78:08:00 SRC=192.168.1.34 DST=192.168.1.100 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=51592 DF PROTO=TCP SPT=51472 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0 Jun 6 02:43:00 xen01 kernel: Intento_SSH: IN=eth0 OUT= MAC=00:16:3e:59:02:bf:00:40:f6:2c:53:78:08:00 SRC=192.168.1.34 DST=192.168.1.100 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=51593 DF PROTO=TCP SPT=51472 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0
También habréis notado, que con estas reglas, el servidor no es capaz de conectarse a si mismo:
ping localhost
PING localhost (127.0.0.1) 56(84) bytes of data. ping: sendmsg: Operation not permitted ping: sendmsg: Operation not permitted ping: sendmsg: Operation not permitted
Tenemos que permitir la conexiones hacia si mismo, pues muchos servicios interactuan con otros por tcp/ip:
#Habilitaos loopback iptables -A INPUT -i lo -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT
Puede que también queramos monitorizar nuestro servidor mediante icmp desde un sitio remoto, o incluso el propio proveedor del servidor dedicado, nos esté monitorizando. Tomaremos ciertas medidas, para evitar un DoS básico, y es no aceptar mas de un paquete icmp por segundo:
#Permitimos que nos hagan ping y responder a este: iptables -A INPUT -p ICMP --icmp-type 8 -m limit --limit 1/s -j ACCEPT iptables -A OUTPUT -p ICMP --icmp-type 0 -m limit --limit 1/s -j ACCEPT
A nivel de seguridad, no es aconsejable instalar mas un servicio en un servidor. Si a parte de servir webs, también enviamos correo, nos tendrémos que proteger de las dos cosas y estaremos el doble de expuestos. No obstante, el ftp está muy asociado al servidor web, pues lo necesitarán los webmasters para subir sus páginas. Si es posible, al igual que el ssh, yo solo habilitaría desde la ip del webmaster:
#Permitimos el acceso por ftp a la ip del webmaster y cortamos al resto registrandolo: iptables -A INPUT -p TCP --dport 20:21 -s $IPWEBMASTER -d $IPWEB -m state --state NEW -j ACCEPT iptables -A INPUT -p TCP --dport 20:21 -j LOG --log-prefix "Intento_FTP: " iptables -A INPUT -p TCP --dport 20:21 -j DROP
Como sabéis, el protocolo ftp utiliza dos puertos, además la conexión puede ser pasiva o activa. Esto puede llegar a complicarnos la regla, ya que no se tendrá muy claro quien establece la conexión. Para evitar problemas con el ftp, se utiliza el módulo del kernel “ip_conntrack_ftp”. Insertadlo si utilizáis ftp:
modprobe ip_conntrack_ftp
La última regla esencial a tener en cuenta es la que permite a nuestro servidor tener la hora sincronizada. Esto es muy importante. Si tenemos mas de un servidor, intentad sincronizarlos todos con el mismo servidor ntp. Lo apreciaréis cuando os entrén en el servidor (si, pese a todas estas medidas, aun se puede entrar, ya analizaremos algunas entradas):
#Permitimos al servidor sincronizar su hora: iptables -A OUTPUT -p UDP --dport 123 -d $IPNTP -m state --state NEW -j ACCEPT
Esa es la única regla de salida del servidor que permito. Muchos optan por establecer la politica de OUTPUT en ACCEPT, yo creo que es casi tan peligroso permitir la salida a cualquier lado como la entrada. Necesitaremos salir al exterior para hacer las actualizaciones del sistema. Pero para ello podemos añadir una regla a mano y quitarla cuando hayamos terminado, dependiendo si actualizamos por ftp o http:
iptables -A OUTPUT -p TCP --dport 80 -d es.debian.org -m state --state NEW -j ACCEPT
apt-get update
apt-get upgrade
iptables -D OUTPUT -p TCP --dport 80 -d es.debian.org -m state --state NEW -j ACCEPT
También es posible que todos los días os enviéis un correo desde el servidor a vuestra cuenta, por ejemplo el logwatch. También tendréis que tenerlo en cuenta a la hora de enviar el mail, porque no será capaz de salir, hará falta añadir una regla y quitarla.
Si tenéis espacio suficiente en disco, quiza querrais registrar todo lo que no ha pasado por las reglas anteriores:
#Log de todo lo que no se permite: iptables -A INPUT -j LOG --log-prefix "Default_Drop: " iptables -A OUTPUT -j LOG --log-prefix "Default_Drop: "
Lamentablemente no todos tienen ips fijas que podamos meter en nuestro script. Habilitarlo a todo el mundo, en casos como ssh o ftp no es una buena opción. Para las ips dinámicas utilizo otra opción: knockd.
El script completo:
#!/bin/bash
#Borramos antiguas reglas.
iptables -F
#HOSTS
IPWEB=192.168.1.100
IPWAN=192.168.1.100
IPADMIN=192.168.1.33
IPWEBMASTER=192.168.1.34
IPNTP=192.168.168.1
#Cargamos el módulo ip_conntrack_ftp sino lo está ya.
if [ `lsmod |grep -c ip_conntrack_ftp` -eq "0" ]
then
modprobe ip_conntrack_ftp
fi
case “$1″ in
start)
#Politíca por defecto:
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP
#Permitimos paquetes de conexiones previamente establecidas:
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
#Habilitaos loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
#Permitimos el acceso por ssh a nuestra ip y cortamos al resto registrandolo:
iptables -A INPUT -p TCP --dport 22 -s $IPADMIN -d $IPWAN -m state --state NEW -j ACCEPT
iptables -A INPUT -p TCP --dport 22 -j LOG --log-prefix “Intento_SSH: ”
iptables -A INPUT -p TCP --dport 22 -j DROP
#Permitimos la entrada por el puerto 80 TCP:
iptables -A INPUT -p TCP --dport 80 -d $IPWEB -m state --state NEW -j ACCEPT
#Permitimos que nos hagan ping y responder a este:
iptables -A INPUT -p ICMP --icmp-type 8 -m limit --limit 1/s -j ACCEPT
iptables -A OUTPUT -p ICMP --icmp-type 0 -m limit --limit 1/s -j ACCEPT
#Permitimos el acceso por ftp a la ip del webmaster y cortamos al resto registrandolo:
iptables -A INPUT -p TCP --dport 20:21 -s $IPWEBMASTER -d $IPWEB -m state --state NEW -j ACCEPT
iptables -A INPUT -p TCP --dport 20:21 -j LOG --log-prefix “Intento_FTP: ”
iptables -A INPUT -p TCP --dport 20:21 -j DROP
#Permitimos al servidor sincronizar su hora:
iptables -A OUTPUT -p UDP --dport 123 -d $IPNTP -m state --state NEW -j ACCEPT
#Log de todo lo que no se permite:
iptables -A INPUT -j LOG --log-prefix “Default_Drop: ”
iptables -A OUTPUT -j LOG --log-prefix “Default_Drop: ”
;;
stop)
#Borramos antiguas reglas del firewall
iptables -F
#Ponemos la politica en ACCEPT
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
#Quitamos el m�dulo ip_conntrack_ftp
modprobe -r ip_conntrack_ftp
;;
restart)
/etc/init.d/fw.sh stop
/etc/init.d/fw.sh start
;;
*)
echo “Uso: /etc/init.d/fw.sh {start|stop|restart}”
;;
esac






[...] cd / Experiencias de un SysAdmin en Debian « Asegurando Servidor Web I iptables [...]
[...] de la base de que el servidor tiene iptables configurado como comentamos en el articulo Asegurando Servidor Web I iptables. Por lo que un intento de acceso por ssh deberia de ser rechazado desde una ip no definida en el: [...]