Lack of protection against brute force attacks - Python

Lack of protection against brute force attacks - Python

Need

Implementation of strong and effective protection against brute force attacks

Context

  • Usage of Python 3 for developing applications and scripts
  • Usage of Django for building web applications

Description

Non compliant code

        from django.contrib.auth import authenticate, login

def login_view(request):
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']
        user = authenticate(request, username=username, password=password)
        if user is not None:
            login(request, user)
            return HttpResponse("User logged in")
        else:
            return HttpResponse("Invalid credentials")
    else:
        return render(request, 'login.html')
        
        

This code is a simple Django view for a login page. It takes a username and password from a POST request, authenticates them, and logs the user in if the credentials are correct.

The vulnerability lies in the fact that there is no protection against brute force attacks. A malicious user could use an automated script to send POST requests with different username and password combinations until they find a pair that works.

There are no measures in place to limit the number of login attempts or introduce a delay after a certain number of failed attempts. This means that an attacker could potentially make unlimited attempts to guess a user's credentials, increasing the likelihood that they will eventually succeed.

This lack of protection against brute force attacks makes the system vulnerable to unauthorized access, which could lead to data breaches and other security issues.

Steps

  • Implement a limit on the number of failed login attempts. After a certain number of failed attempts, the account should be temporarily locked.
  • Introduce a delay after each failed login attempt. This delay should increase exponentially with each subsequent failed attempt, making brute force attacks time-consuming and less feasible.
  • Use CAPTCHA to distinguish between human users and bots. This can prevent automated scripts from carrying out brute force attacks.
  • Implement multi-factor authentication. This adds an additional layer of security, as even if the attacker guesses the password, they would still need the second factor (like a text message or email confirmation) to gain access.
  • Monitor and log login attempts. This can help in identifying suspicious activity and taking appropriate action.

Compliant code

        from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import ratelimit
from django.core.cache import cache
from django.http import HttpResponse
from django.shortcuts import render
from django.views.decorators.debug import sensitive_post_parameters
from django_ratelimit.decorators import ratelimit

@sensitive_post_parameters('password')
@ratelimit(key='ip', rate='5/m')  # limit to 5 attempts per minute
def login_view(request):
    was_limited = getattr(request, 'limited', False)
    if was_limited:
        return HttpResponse("Too many failed login attempts. Please try again later.")
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']
        user = authenticate(request, username=username, password=password)
        if user is not None:
            login(request, user)
            return HttpResponse("User logged in")
        else:
            failed_attempts = cache.get('failed_login_attempts_{}'.format(username), 0)
            failed_attempts += 1
            cache.set('failed_login_attempts_{}'.format(username), failed_attempts, 60*5)  # 5 minutes
            if failed_attempts > 5:
                return HttpResponse("Too many failed login attempts. Please try again later.")
            return HttpResponse("Invalid credentials")
    else:
        return render(request, 'login.html')
        
        

The updated code includes several measures to protect against brute force attacks:

- The ratelimit decorator from the django_ratelimit library is used to limit the number of login attempts from a single IP address. In this case, the rate is set to 5 attempts per minute. If this limit is exceeded, the user will receive a message to try again later.

- The sensitive_post_parameters decorator is used to prevent the password from being included in any logging.

- The number of failed login attempts for each username is tracked using Django's cache framework. If a user fails to log in more than 5 times in a 5-minute period, they will be temporarily locked out and receive a message to try again later.

This approach slows down brute force attacks by introducing delays and limits, making them less feasible. It also provides feedback to users about failed login attempts, which can help them identify and respond to unauthorized access attempts.

References