Listas dinámicas de bloqueo de IP con NGINX Plus y fail2ban

NGINX | 19 de septiembre de 2017

Foto: Arnold Reinhold – Obra propia, CC BY‑SA 3.0

Puede que no sea consciente de ello, pero su sitio web está constantemente amenazado. Si utiliza WordPress, los bots intentan enviarle spam. Si tiene una página de inicio de sesión, se producen ataques de fuerza bruta para averiguar la contraseña. También puede considerar a los rastreadores de los motores de búsqueda como visitantes no deseados.

Proteger su sitio frente a actividades no deseadas, sospechosas y maliciosas no es tarea fácil. Los firewalls de aplicaciones web, como NGINX App Protect y los módulos certificados de NGINX Plus disponibles a través de nuestros socios, son herramientas eficaces que deben formar parte de su estrategia de seguridad. En la mayoría de los entornos, nunca hay demasiada seguridad, y un enfoque multicapa es siempre el más eficaz.

En esta entrada del blog, hablaremos del uso de fail2ban como una capa adicional de la infraestructura de seguridad web. Fail2ban es un sistema de detección de intrusiones (IDS) que supervisa continuamente los archivos de registro en busca de actividad sospechosa y, a continuación, ejecuta una o varias acciones preconfiguradas. Por lo general, fail2ban detecta los intentos fallidos de inicio de sesión y bloquea (veta) durante un periodo de tiempo la dirección IP responsable. Se trata de una defensa sencilla pero eficaz contra los ataques de fuerza bruta dirigidos a contraseñas.

En NGINX Plus R13 presentamos un almacén nativo de pares clave-valor y una nueva API de NGINX Plus. Esto permite implementar numerosas soluciones de configuración dinámica en las que un sistema externo puede controlar la configuración y el comportamiento de NGINX Plus. En esta entrada del blog, también mostraremos cómo se puede utilizar fail2ban para reconfigurar automáticamente NGINX Plus de modo que ignore las solicitudes procedentes de direcciones IP que hayan provocado varios eventos de autenticación fallida.

Nota del editor: En NGINX Plus R16 y versiones posteriores, los almacenes de clave-valor se pueden sincronizar entre todas las instancias de NGINX Plus de un clúster. (El uso compartido del estado en un clúster también está disponible para otras funciones de NGINX Plus). Para obtener más información, consulte nuestro blog y la Guía de administración de NGINX Plus.

Uso de un almacén de clave-valor para crear una lista de bloqueo de direcciones IP

El almacén de clave-valor de NGINX Plus es un almacén nativo en memoria con tres características principales:

  1. Los pares clave-valor se representan como objetos JSON
  2. Los pares clave-valor se gestionan íntegramente a través de una API
  3. Los valores están disponibles para NGINX Plus como variables de configuración normales

El almacén de clave-valor se define creando una zona de memoria compartida cuyo nombre se especifica mediante la directiva keyval_zone. A continuación, este almacén de clave-valor se puede rellenar con un conjunto inicial de valores mediante el método HTTP POST para enviar un objeto JSON a la API. La directiva keyval define qué variable existente se utilizará como clave de búsqueda ($remote_addr en el ejemplo) y el nombre de una nueva variable ($num_failures) que se evaluará a partir del valor correspondiente a dicha clave.

Configuración y gestión del almacén de clave-valor de NGINX Plus

La API se habilita designando un bloque location como punto de conexión de la API de NGINX Plus.


server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;

server {
    listen 80;

    location / {
        root /usr/share/nginx/html;
        if ($num_failures) {
            return 403;
        }
    }
}

vim: syntax=nginx

Antes de añadirle pares clave-valor, una solicitud del contenido del almacén denylist devuelve un objeto JSON vacío.

$ curl http://localhost:1111/api/6/http/keyvals/denylist{}

Ahora se puede rellenar el almacén de clave-valor con un par inicial mediante el método HTTP POST (usando el argumento -d del comando curl) para enviar un nuevo objeto JSON. Se pueden enviar mediante POST varios pares de clave-valor a un almacén vacío y, a partir de entonces, solo de uno en uno.

$ curl -iX POST -d '{"10.0.0.1":"1"}' http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 201 Created
...

Un par clave-valor se elimina haciendo una petición PATCH sobre la clave con el valor null.

$ curl -iX PATCH -d '{"10.0.0.1":null}' http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...

Todos los pares clave-valor se pueden eliminar del almacén de clave-valor enviando el método «DELETE».

$ curl -iX DELETE http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...

Ahora se puede configurar una implementación sencilla de listas de bloqueo de direcciones IP.


server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;

server {
    listen 80;

    location / {
        root /usr/share/nginx/html;
        if ($num_failures) {
            return 403;
        }
    }
}

vim: syntax=nginx

Este fragmento de configuración configura el puerto 80 como servidor web. La línea 18 asigna a la variable $num_failures el valor del par clave-valor de la zona de memoria compartida denylist cuya clave coincide con el valor de la variable $remote_addr (dirección IP del cliente). Este proceso de evaluación de $num_failures se expresa de forma secuencial con mayor claridad:

  1. Pase el valor de $remote_addr al almacén de pares clave-valor denylist key‑value store
  2. Si $remote_addr coincide exactamente con una clave, obtenga el valor de ese par clave-valor
  3. Devuelve el valor como $num_failures

Por tanto, si la dirección IP del cliente se ha enviado mediante POST al almacén de clave-valor, todas las solicitudes generan un error HTTP 403 Forbidden. La ventaja de este enfoque, frente al uso de un bloque map con el mismo fin, es que un sistema externo puede controlar la lista de direcciones IP bloqueadas.

Uso de fail2ban para gestionar dinámicamente la lista de bloqueo de direcciones IP

Como se ha mencionado anteriormente, fail2ban se utiliza habitualmente para detectar actividades sospechosas o maliciosas en los archivos de registro y, a continuación, tomar medidas para proteger el sistema. La acción predeterminada consiste en configurar iptables para que descarte todos los paquetes procedentes de la dirección IP registrada. Aunque este enfoque resulta eficaz para bloquear a un agente malicioso, ofrece una experiencia muy deficiente a los usuarios legítimos, especialmente si han olvidado su contraseña. Para estos usuarios, parece que el sitio web simplemente no está disponible.

La siguiente configuración describe una acción de fail2ban que envía la dirección IP infractora al almacén de clave-valor de lista de denegación de NGINX Plus. A continuación, NGINX Plus muestra una página de error más útil y, al mismo tiempo, aplica un límite de frecuencia de solicitudes a esa dirección IP, de modo que, si las acciones forman parte de un ataque, este queda neutralizado de forma efectiva.

Suponemos una instalación predeterminada de fail2ban en el mismo host que NGINX Plus, con toda la configuración en el directorio /etc/fail2ban.

La siguiente acción de fail2ban utiliza la API de NGINX Plus para añadir y eliminar direcciones IP «bloqueadas» en el almacén de clave-valor denylist, del mismo modo que en el sencillo ejemplo anterior. Colocamos el archivo nginx-plus-denylist.conf en el directorio /etc/fail2ban/action.d.


[Definition]
actionban = curl -s -o /dev/null -d '{"":""}' http://localhost:1111/api/6/http/keyvals/denylist
actionunban = curl -s -o /dev/null -X PATCH -d '{"":null}' http://localhost:1111/api/6/http/keyvals/denylist

La siguiente jaula de fail2ban habilita el filtro integrado que detecta los intentos de inicio de sesión fallidos mediante el módulo de autenticación básica HTTP de NGINX y aplica la acción definida en nginx-plus-denylist.conf. Hay muchos otros filtros integrados disponibles y se pueden crear fácilmente para detectar actividad no deseada en los registros de acceso de NGINX.


[DEFAULT]
bantime   = 120
banaction = nginx-plus-denylist
 
[nginx-http-auth]
enabled = true

El siguiente fragmento de configuración de NGINX Plus aplica la autenticación HTTP básica a la página predeterminada «Welcome to NGINX». Colocamos el archivo password_site.conf en el directorio /etc/nginx/conf.d.


server {
    listen 1111;
    allow  127.0.0.1; # Only allow access from localhost,
    deny   all;       # and prevent remote access.

    location /api {
        api write=on; # The NGINX Plus API endpoint in read/write mode
    }
}

keyval_zone zone=denylist:1M state=denylist.json;
keyval $remote_addr $num_failures zone=denylist;

limit_req_zone $binary_remote_addr zone=20permin:10M rate=20r/m;

server {
    listen 80;
    root /usr/share/nginx/html;

    location / {
        auth_basic "closed site";
        auth_basic_user_file users.htpasswd;

        if ($num_failures) {
            rewrite ^.* /banned.html;
        }
    }

    location = /banned.html {
        limit_req zone=20permin burst=100;
    }
}

vim: syntax=nginx

La línea 11 define keyval_zone como en el archivo denylist_keyval.conf anterior, pero añade el parámetro state, que especifica un archivo en el que se guarda el estado del almacén de clave-valor, de modo que se conserva cuando NGINX Plus se detiene y se reinicia. El almacén de clave-valor se conserva durante las recargas normales de la configuración sin necesidad de especificar un archivo de estado. (Las líneas 1–10 no se muestran, pero son las mismas que en denylist_keyval.conf.)

La línea 14 crea una zona de memoria compartida denominada 20permin y especifica una tasa máxima de 20 solicitudes por minuto para cada dirección IP de cliente. Este límite de tasa se aplica cuando fail2ban añade una dirección IP a la lista de denegación (línea 30).

El bloque server define un servidor web que escucha en el puerto predeterminado (80), donde todas las solicitudes coinciden con el bloque location / (línea 20). La directiva root (línea 18) especifica dónde se aloja nuestro contenido web. Las líneas 21–22 indican que este sitio requiere autenticación HTTP básica y que la base de datos de contraseñas de los usuarios autorizados se encuentra en el archivo users.htpasswd. La documentación de la directiva auth_basic_user_file describe el formato de dicho archivo. Tenga en cuenta que esta configuración no está cifrada, ya que se utiliza con fines de prueba, y que cualquier sitio web de producción que utilice autenticación HTTP básica debe usar SSL/TLS para garantizar la seguridad del transporte.

Cuando la dirección IP del cliente se ha incluido en la lista de bloqueos (líneas 24–26), en lugar de devolver el código de error HTTP 403, reescribimos la solicitud como /banned.html, que después se procesa en el bloque location que coincide exactamente con ese URI (línea 29). Esto devuelve una página web estática que explica que la dirección IP del usuario se ha bloqueado debido a un número excesivo de intentos de inicio de sesión fallidos. También aplica un límite de solicitudes restrictivo (línea 30) que impide que un cliente malicioso consuma recursos del sistema innecesariamente.

Página que ven los usuarios cuando su dirección IP está en la lista de bloqueados

(El código HTML de esta página web de ejemplo está disponible como banned.html en el repositorio de GitHub de esta publicación.)

Podemos simular varios intentos de inicio de sesión fallidos consecutivos y la actividad correspondiente de fail2ban de la siguiente manera.

$ curl -i http://admin:password@www.example.com/HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:admin@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:pass1234@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:letmein@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:fred@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl http://admin:P@ssw0rd@www.example.com/
<!DOCTYPE html>
<html>
<head>
<title>Banned</title>
...
$ tail -f /var/log/fail2ban.log
2017-09-15 13:55:18,903 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:55:28,836 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:49,228 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:50,286 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,015 fail2ban.filter   [28498]: INFO    [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,088 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Ban 172.16.52.1
2017-09-15 13:59:52,379 fail2ban.actions  [28498]: NOTICE  [nginx-http-auth] Unban 172.16.52.1

Esta solución dinámica de bloqueo de direcciones IP ya puede funcionar sin necesidad de realizar más cambios de configuración. Fail2ban supervisa los archivos de registro de NGINX y añade las direcciones IP bloqueadas al almacén de clave-valor de NGINX Plus mediante la API. Transcurridos 120 segundos (el valor de bantime configurado en jail.local), la dirección IP infractora se elimina de la lista de bloqueo, de nuevo mediante la API de NGINX Plus, y se vuelven a aceptar los intentos de inicio de sesión desde esa dirección.

La API y el almacén de clave-valor de NGINX Plus hacen posible este tipo de integración. Ahora se pueden implementar soluciones de configuración avanzadas sin necesidad de crear archivos de configuración de NGINX Plus ni realizar recargas. Nos encantaría saber cómo utiliza estas funciones para crear sus propias soluciones de configuración dinámica. Deje un comentario a continuación.

Para probar NGINX Plus, inicie hoy mismo su prueba gratuita de 30 días o póngase en contacto con nosotros para hablar sobre sus casos de uso.


Compartir

Sobre el autor

Liam Crilly
Liam CrillyDirector sénior de Gestión de Producto

Más artículos de Liam Crilly

Artículos relacionados del blog

Únete a F5 NGINX en AppWorld 2024
NGINX | 04/01/2024

Únete a F5 NGINX en AppWorld 2024

Descubre las numerosas razones para asistir al evento insignia de F5, que incluirá un emocionante anuncio sobre la cartera de productos NGINX.

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source
NGINX | 26/09/2023

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source

La función de diccionario compartido permite configurar NGINX sin necesidad de reiniciarlo al renovar los certificados SSL/TLS.

Exposición de APIs en Kubernetes
NGINX | 10/03/2022

Exposición de APIs en Kubernetes

La unidad 2 de Microservices March 2022 se centra en cómo exponer tus API en Kubernetes. Aquí tienes enlaces a los recursos que necesitas: la retransmisión en directo del 14 de marzo, artículos de blog y otros recursos de preparación, y el laboratorio práctico «Protege las API de Kubernetes mediante la limitación de solicitudes».

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto
NGINX | 23/08/2021

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto

Hoy, en NGINX Sprint 2.0, anunciamos cómo respaldaremos nuestra visión del futuro de las aplicaciones modernas con una mayor presencia en la comunidad de código abierto, incluida la participación en el proyecto comunitario Kubernetes, nuevos proyectos de software de código abierto y la arquitectura de referencia para aplicaciones modernas.

Presentamos NGINX Plus R22
NGINX | 09/06/2020

Presentamos NGINX Plus R22

NGINX Plus R22 incorpora nuevas funcionalidades, como la compatibilidad con OCSP para verificar la revocación de certificados en TLS mutuo, el uso de varios proveedores de identidad OIDC en una configuración, el seguimiento en tiempo real de la limitación de solicitudes y conexiones en el panel de NGINX Plus y mejoras en NGINX JavaScript.

Presentamos NGINX Plus R17
NGINX | 11/12/2018

Presentamos NGINX Plus R17

NGINX Plus R17 incorpora compatibilidad con la limitación de velocidad en dos fases y con TLS 1.3, la versión más reciente del protocolo Transport Layer Security. La configuración de OpenID Connect es más sencilla y NGINX ModSecurity WAF es el doble de rápido que antes. También se ha actualizado el módulo JavaScript de NGINX.