Rasguñando Netfilter (parte 3)

En la parte anterior, vimos cómo configurar un cortafuegos minimalista básico, habilitando acceso a un servicio. En esta parte pretendo mostrar formas de asegurar el acceso a los servicios, y también cómo monitorear lo que va ocurriendo, en tiempo real.

¿Cuánto nos protege realmente un cortafuegos?

Podría decirse que la mayoría de los intentos de vulnerar nuestro equipo pueden provenir de paquetes de red entrantes desde el exterior. A los efectos prácticos de este artículo, es lo que generalmente se asumirá, y será en este sentido que oriente mis recomendaciones.

Pero no necesariamente tiene por qué ocurrir siempre así; toda la seguridad de nuestras reglas entrantes puede vulnerarse si en nuestro equipo introducimos memorias USB y ejecutamos programas desconocidos, abrimos y ejecutamos archivos sospechosos adjuntados en mensajes de correo provenientes de contactos desconocidos (a veces, incluso conocidos), o hacemos caso de los correos electrónicos que alegan que debemos proporcionar nuestro nombre de usuario y contraseña o en caso contrario perderemos acceso a un supuesto buzón de correo o cuenta bancaria, etc. (un cortafuegos no protege contra la falta de sentido común).

Para resumir, un buen cortafuegos es solamente una de las medidas de seguridad que deben tomarse; no la única. Como estos artículos tratan principalmente sobre netfilter, no puede abarcarse todo. No obstante, intentaré abarcar un poco más de contenido que en las partes anteriores de esta serie.

Exposición

Aunque adoptemos medidas para asegurar nuestra red, es importante comprender la importancia de habilitar sólo los servicios realmente necesarios, especialmente los servicios públicos. Esto es particularmente importante en el caso de servicios potencialmente vulnerables, como el servicio NTP.

Este servicio utiliza el protocolo UDP, un protocolo que por carecer de control de estado suele utilizarse para ataques de inundación. A continuación lo ejemplificaremos. Supongamos que habilitamos globalmente el servicio NTP, con una regla como esta:

Nuestro equipo podría recibir de Internet una petición monlist en un paquete UDP con una sección de datos de sólo unos 8 bytes, lo cual agregado a los encabezados podría dar un tamaño inferior a 64 bytes, y en cambio el servicio NTP podría responder con un listado de clientes recientes de hasta 100 paquetes UDP con una sección de datos de 440 bytes para cada uno, que agregado a los encabezados resultaría en paquetes de hasta unos 500 bytes. Esto representa un factor de amplificación de un 78125% del tráfico generado por la respuesta en relación al tráfico consumido por la consulta.

Supongamos entonces que un usuario malintencionado de Internet ha identificado nuestro servicio NTP como vulnerable junto con otros 99 servidores en similar situación, y comienza a enviar simultáneamente a todos estos equipos consultas del tipo que acabamos de mencionar a razón de 1 paquete por segundo, pero falseando la dirección IP de origen para colocar en su lugar la dirección IP de un servidor Web que el atacante ha seleccionado como objetivo, cuyo enlace es de 10 mbps.

El atacante habrá generado entonces en su equipo un tráfico saliente manejable incluso para un enlace por modem (64 bytes * 100 equipos * 8 bits / 1000 = 51.2 kbps), y en cambio ello podría provocar una respuesta conjunta de los equipos consultados (entre los cuales se encuentra el nuestro) equivalente a unos 40 mbps (500 bytes * 100 paquetes * 100 equipos * 8 bits / 1000 = 40,000 kbps).

Listemos algunas implicaciones de lo que acaba de ocurrir, para comprender su magnitud:

  • Nuestro propio equipo habrá formado parte de un ciberataque, obviamente sin nuestro consentimiento.
  • El equipo del atacante no aparecería en las trazas de la víctima, por tratarse de un ataque por reflejo.
  • La pobre víctima nada conseguirá bloqueando los paquetes NTP en su cortafuegos, porque sencillamente el enlace estará totalmente saturado, de modo que no tendrá capacidad libre para ofrecer sus servicios a clientes legítimos que deseen acceder a servicio web, por ejemplo. A los efectos de Internet, el servicio web de la víctima estará caído.
  • De contar la víctima con ese único enlace, su saturación le hará perder también la posibilidad de navegar, utilizar el correo electrónico, o en definitiva cualquier servicio que dependa de dicho enlace.
  • En su desesperación por restablecer el servicio, la víctima podría sentirse tentada a crear en su cortafuegos una lista negra con las direcciones IP de los equipos que le envían la inundación de paquetes UDP, pero con un enlace saturado ello sería igualmente inútil y además cortaría efectivamente las comunicaciones con subredes auténticas.
  • De haber contratado la víctima a su ISP un plan de servicios de conectividad con modalidad de pago por tráfico entrante consumido, el ataque podría resultar en gastos no planificados de un monto considerable.
  • De haber tomado la víctima la medida de reportar la dirección IP de los equipos atacantes a los servicios internacionales de listas negras, corremos el riesgo de que nuestra red sea clasificada como maliciosa, y podríamos perder la comunicación con todas las redes que utilicen estas listas negras en la configuración de sus servicios.
  • La víctima podría demandarnos legalmente por las afectaciones a su servicio, y en sus trazas tendría la evidencia para demostrarlo.

Un ataque más sofisticado y con recursos (un atacante con un enlace de 100 mbps utilizando cientos de botnets), habría podido saturar incluso un enlace de 1 gbps.

Si el ISP del atacante tan solo hubiese tomado la medida elemental de impedir el falseo de direcciones IP de origen para el tráfico saliente desde las subredes que administra, el intento de ataque hubiese sido cortado de raíz. Lamentablemente, en el mundo hay una cantidad sorprendentemente alta de proveedores de servicios que no toman todas las medidas a su alcance para evitar que utilicen sus redes y servicios con fines maliciosos.

¿Como podríamos evitar entonces formar parte de tales ataques? Para comenzar, habilitando los servicios potencialmente vulnerables sólo de ser imprescindible y en cualquier caso, solo para aquellos equipos o subredes a quienes están destinados, por ejemplo:

De esta manera, estaremos habilitando el servicio sólo para los clientes conectados a la interfaz LAN (eth0) y cuya dirección ip pertenezca al bloque de direcciones de nuestra subred. En caso que sean servicios que realmente necesiten tener visibilidad pública, podríamos imponer ciertos límites razonables.

Estableciendo límites

Prosigamos con el ejemplo de nuestra aplicación web, utilizando el cortafuegos tal como podría haber quedado configurado al final de la segunda parte:

Dado que deseamos permitir el acceso desde el exterior de nuestra institución, no podemos restringir el servicio solo a la interfaz LAN. El servicio parece estar funcionando bien para la red local, pero .. ¿será seguro permitir un acceso ilimitado desde cualquier parte?

Sucede que no. Un atacante con un enlace de mayor capacidad de transferencia que el nuestro podría elaborar un script que cree incesantemente nuevas conexiones a nuestro servicio web, y eventualmente saturar nuestro enlace o hacer colapsar el servicio web.

Una equivocación frecuente

Podría pensarse que reemplazando la última regla por algo como esto (que recién vi recomendado en una página web en español) se solucionaría el problema:

Pero aunque la coincidencia limit pueda ser útil para las trazas, en este contexto resultará incorrecta por varias razones:

  • El límite es global. No se declara sólo para los paquetes provenientes de clientes que intenten sobrecargar el servicio, de modo que afectará otros clientes válidos.
  • Al no especificarse estados, se evalúan todos los paquetes entrantes con destino al servicio web, lo cual degradará notablemente el rendimiento de nuestra aplicación web.
  • El alto valor del parámetro --limit-burst posterga la entrada en funcionamiento del límite.
  • Los paquetes que rebasen el límite no se descartan explícitamente, por lo que seguirán evaluándose con el resto de las reglas.
  • Como al principio del bloque se permitió acceso pleno a los paquetes de conexiones establecidas, el límite no impedirá que se continúen estableciendo conexiones.

Asumiendo que nuestro equipo tenga por ejemplo la dirección IP 192.168.0.10, podríamos combrobar los efectos del límite anterior, ejecutando desde otro equipo el siguiente comando del paquete apache2-utils, por ejemplo:

Esto intentará establecer 1000 conexiones consecutivas que llegarían al servicio web pese al límite, lo que notablemente ralentizadas (en la máquina virtual que preparé para comprobaciones, la prueba tardó más de 42 minutos). Lo mismo ocurriría con cualquier cliente real que intentase navegar; es decir, este tipo de límite es ineficiente porque ralentiza indiscriminadamente la experiencia de todos los clientes, sean maliciosos o auténticos. Sucede que en una comunicación auténtica, muy pocos recursos consumen los 1500 bytes de tamaño máximo que suele imponer el MTU para un paquete TCP/IP, por lo que estos recursos deben distribuirse en una secuencia de paquetes, cuyo flujo normal se afectará enormemente con un límite global.

Ahora bien, si las reglas del cortafuegos hubiesen estado mejor declaradas para que se forzara la imposición de un límite global de 25 conexiones por minuto al servicio web, de hecho habríamos configurado nuestro cortafuegos de manera idónea para que fuese vulnerable ante ataques de denegación de servicio, pues esta cantidad de paquetes por minuto es fácilmente alcanzable por sólo uno o dos clientes auténticos, de modo que estaríamos impidiendo acceso a los cientos o quizás miles de clientes potenciales que podríamos dar servicios simultáneamente.

Esto demuestra una vez más que no debemos seguir ciegamente cualquier recomendación que encontremos en Internet, debemos intentar comprender realmente tales recomendaciones consultando la documentación y confirmando su funcionamiento mediante pruebas en un entorno virtual seguro, antes de implementar las reglas en un equipo en producción.

Una mejor forma de hacerlo

Lo correcto sería limitar los intentos de conexión sólo para aquellos clientes que pretendan sobrecargar el servicio, lo cual podemos conseguir mediante la coincidencia hashlimit; pero además, conviene tener al menos una idea aproximada de lo que se está sirviendo para otorgar a los clientes recursos suficientes, pero no ilimitados.

Supongamos que nuestra aplicación web consta de 10 archivos php, 5 archivos js, 3 archivos css, 2 archivos de tipografía eot y no mas de 30 imágenes jpg. Quizás parezca mucho, pero en primer lugar, el navegador no tiene por que cargarlo todo de una vez, y en segundo lugar, una vez se carguen los recursos en la memoria del navegador, las conexiones suelen cerrarse automáticamente. Es decir, que incluso si en determinado momento existen 50 conexiones concurrentes, no necesariamente por ello serán conexiones persistentes.

De todas maneras, demos un margen de seguridad para posibles reintentos de carga y también para el crecimiento de nuestra aplicación en el futuro próximo, y asumamos que permitiremos desde cada cliente no más de 100 conexiones establecidas concurrentes.

El cortafuegos podría entonces quedar configurado de la siguiente manera:

Explicaré un poco lo que se busca con esta recomendación. Se utilizarían dos límites diferentes. La regla que evalúa las peticiones al servicio web (los paquetes con estado NEW destinados al puerto tcp 80), aceptará mediante hashlimit sólo 100 paquetes consecutivos en ráfaga y luego impondrá una demora a los nuevos paquetes, que se aceptarán mientras no excedan los 200 permitidos para ese minuto, y sólo gradualmente, a medida que expire el tiempo de vida (en este caso, 15 segundos) de los que se aceptaron en ráfaga. Por cierto, este tipo de límite se aplicará de manera independiente para cada cliente basándose en dirección ip de origen, dirección ip de destino, y puerto de destino de los paquetes.

La segunda parte evaluará las conexiones ya establecidas (los paquetes con estado ESTABLISHED destinados al puerto tcp 80), y no limitará la cantidad de paquetes por unidad de tiempo (lo cual como vimos ralentizaría innecesariamente la navegación), sino que aceptará sólo la cantidad máxima de conexiones concurrentes que un mismo cliente puede mantener con el servidor (en este caso, 100). Nótese que inmediatamente detrás de esta regla se ha colocado otra de seguridad que descartará cualquier otro paquete (independientemente de su estado) que utilice el protocolo TCP y esté destinado al puerto 80. De esta manera, se evitará que un paquete sea evaluado con reglas posteriores que le permitan el acceso, burlando nuestros límites.

Con la configuración anterior, aunque cientos de equipos intenten sobrecargar nuestro servidor, no deberían conseguirlo (al menos en lo relativo a las reglas del cortafuegos). Además, el equipo debería ser capaz de ofrecer el servicio a clientes auténticos sin que noten mayores afectaciones. De hecho, en la máquina virtual que preparé para comprobaciones, la misma prueba anteriormente realizada con el comando ab, terminó esta vez en sólo 4 minutos con 30 segundos, lo cual demuestra que este acercamiento resulta más eficiente.

Evidentemente, las reglas anteriores no protegen contra ataques distribuidos de denegación de servicios que busquen saturar el canal, para lo cual generalmente será necesario la intervención del ISP. Pero aun así, estas reglas permiten un nivel de protección básico.

En todo caso, siempre conviene monitorear regularmente que las reglas del cortafuegos se estén aplicando como pretendemos, sobre lo cual hablaré brevemente a continuación.

Monitoreando

Existen varias formas de monitorear en netfilter; personalmente prefiero separar las trazas del cortafuegos hacia un archivo separado para poder luego visualizarlo en tiempo real. Para esto, suelo utilizar los paquetes ulogd2 y multitail, que se instalan fácilmente y funcionan sin necesidad de configuración adicional:

Una vez instalados, declarar las reglas que habilitan el monitoreo es fácil. En este caso, por ejemplo, si deseásemos registrar todas las peticiones enviadas al nuestro servicio web, podría insertarse antes de la regla pertinente una regla de monitoreo con el destino de salto NFLOG, al cual también se le puede declarar un prefijo personalizado, que luego aparecerá en el archivo de trazas:

Para visualizar entonces en tiempo real lo que ocurre, recomiendo multitail porque no sólo permite la visualización de varias trazas en tiempo real, sino que además soporta resaltado de sintaxis, posibilidad de realizar búsquedas y filtrar los resultados, etc. Para usarlo, basta con ejecutar el siguiente comando, por ejemplo:

Incluso, para simplificarnos la vida, podríamos declarar todo esto como un alias:

De esta manera, podremos invocar el monitoreo en cualquier momento que deseemos, ejecutando simplemente fwlog.

Presionando la tecla h accederemos a la ayuda en línea de multitail, y presionando la letra q saldremos a la terminal. Para más detalles, puede consultarse la página de manual, como de costumbre:

(Continuará)

Probablemente este artículo haya resultado más largo y complejo que los anteriores, pero es natural, a medida que se intentan configurar reglas que cubran necesidades más específicas. Si notan algún error, por favor, no dejen de reportarlo en un comentario.

En la próxima parte de este tema, mostraré cómo proteger razonablemente nuestro equipo contra posibles falseos de direcciones de origen, entre otras medidas de seguridad.

(Visited 44 times, 1 visits today)
Hugo Florentino
Sobre Hugo Florentino 7 Artículos
Administrador de redes y sistemas. Usuario regular de GNU/Linux desde Octubre de 2008. Miembro fundador del Grupo de Usuarios de Tecnologías Libres (GUTL).

Sé el primero en comentar

Dejar una contestacion

Tu dirección de correo electrónico no será publicada.


*