Tutorial

Protege tu sitio con NGINX de Ataques por Fuerza Bruta

Si usas Nginx como "Proxy inverso" para gestionar las peticiones de tu aplicación en Django, Flask, Ruby, entre otros...con este tutorial puedes proteger las Urls mas importantes y evitar ataques mal intencionados.

La seguridad de tus aplicaciones o sitios web es importante, sobre todo porque nunca sera suficiente, no importa cuanto te esfuerces siempre habrá algún detalle que pueda perjudicar tu sistema. En la actualidad el uso de Frameworks a reducido en gran parte el trabajo de aprender detalles sobre la construcción y levantamiento de un sitio, a pesar de que cuente con una estructura solida de seguridad, no sera suficiente para proteger todas aquellas peticiones.

En este tutorial, voy a tomar Django como Framework y vamos a proteger la url del administrador de Django que viene por defecto.

from django.conf.urls import patterns, include, url
from django.contrib import admin

urlpatterns = patterns('',

    url(r'^admin/', include(admin.site.urls)),


)

Cuando vamos al navegador e ingresamos http://midominio.com/admin vemos algo como esto.

Admin Django

Por defecto Django trae las protecciones o middlewares para validar los formularios y que estos tengan un token de seguridad, pero no es suficiente...se puede crear un script para automatizar el logeo sin problemas e intentar distintas contraseñas a partir de un diccionario.

Normalmente usaríamos una aplicación que haga el trabajo desde las vistas como por ejemplo django-ratelimit , pero el objetivo de este tutorial es abarcar la solución para los demas Frameworks que usen NGINX.

Entonces vamos a comenzar por ubicar nuestro archivo dentro la carpeta sites-available de nginx correspondiente a nuestra app el cual contiene algo parecido a esto:

upstream mi_app {
  server unix:/webapps/django/run/gunicorn.sock fail_timeout=0;
}

server {
    listen   80;
    server_name app.test;

    client_max_body_size 4G;

    access_log /webapps/django/logs/nginx-access.log;
    error_log /webapps/django/logs/nginx-error.log;

    location /static/ {
        alias   /webapps/django/static/;
    }

    location /media/ {
        alias   /webapps/django/media/;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        if (!-f $request_filename) {
            proxy_pass http://mi_app;
            break;
        }
    }
}

NGINX cuenta con una directiva llamada limit_req_zone, la cual nos permite restringir el numero de peticiones por segundo que son permitidas en una url (location), provenientes de una misma IP, es decir que con esto controlaremos aquellos Bots que intentan atacar nuestro sitio con peticiones concurrentes.

Dentro de nuestro archivo de configuracion debemos de incluir la siguiente linea:

limit_req_zone $binary_remote_addr zone=admin:10m rate=1r/s;

Con esta linea le indicamos a NGINX que vamos a crear un espacio de memoria compartida en el servidor llamada "admin" con capacidad de 10MB para almacenar las direcciones IPs que intenten ingresar a nuestra URL. Por ultimo indicamos la tasa de concurrencia permitida que sera de 1 solicitud por segundo (el tiempo promedio de una solicitud normal).

Ahora vamos aplicar la restricción a la URL que vamos a bloquear dentro de un location, el cual debe tener la URL por defecto que tenemos en el archivo urls.py de nuestro proyecto de Django.

    location /admin/ {
        # aplicando directiva
        limit_req zone=admin burst=5;
    
        # repetimos el reverse proxy del location principal
        proxy_pass http://mi_app;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
    }

Si observamos el código, en donde aplicamos la restricción indicamos que zona compartida vamos a utilizar, la cual sera admin (la creamos anteriormente) y con burst limitamos el acceso a partir del quinto intento fuera del rango de tolerancia permitido por una IP (1 petición por segundo).

Desgraciadamente tenemos que repetir las instrucciones del reverse proxy del location principal, porque de lo contrario NGINX no sabría donde dirigir dicha solicitud (es un mal necesario).

El archivo final nos ha quedado de la siguiente manera:

upstream mi_app {
  server unix:/webapps/django/run/gunicorn.sock fail_timeout=0;
}

limit_req_zone $binary_remote_addr zone=admin:10m rate=1r/s;

server {
    listen   80;
    server_name app.test;

    client_max_body_size 4G;

    access_log /webapps/django/logs/nginx-access.log;
    error_log /webapps/django/logs/nginx-error.log;

    location /static/ {
        alias   /webapps/django/static/;
    }

    location /media/ {
        alias   /webapps/django/media/;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        if (!-f $request_filename) {
            proxy_pass http://mi_app;
            break;
        }
    }
    location /admin/ {
        # aplicando directiva
        limit_req zone=admin burst=5;
    
        # repetimos el reverse proxy del location principal
        proxy_pass http://mi_app;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
    }
}

Ahora lo que nos queda por hacer es guardar los cambios de nuestra configuracion y actualizar nuestro NGINX para probar nuestro pequeña regla de seguridad. Quizás parezca un poco rudimentaria y algo tediosa la manera de hacer esto desde NGINX cuando se tienen librerías en el framework, pero la solución es trivial.

Compartelo en:    

Acerca del Autor

Aarón Díaz R Software Developer

Soy desarrollador de software con experiencia en bases de datos y lenguajes de programación como Python, Java SE, Javascript, C y PHP.

  Comentarios



"El ser de las cosas, no su verdad, es la causa de la verdad en el entendimiento."

- Santo Tomás de Aquino