Missing limits on Django login attempts

Missing limits on Django login attempts

Description

A Django login flow that only calls authenticate and login, without controls such as failure limits, delays or lockouts, lets an attacker repeatedly try passwords.

Potential impact

  • An attacker may guess an account password through brute force.
  • Authentication failures may be difficult to detect or block.

Remediation

  • Track failed login attempts by account, IP address and device.
  • Apply a verified defense layer such as django-ratelimit or django-axes.

Examples

These are excerpts from inside a login function. check_login_attempts must be implemented by the application or connected to a verified defense layer; adding the call alone does not create a limit. Record authentication failures and use them when deciding whether to allow subsequent attempts.

Before

python
user = authenticate(username=username, password=password)
if user is not None:
    login(request, user)

After

python
if not check_login_attempts(request):
    return None
user = authenticate(username=username, password=password)
if user is not None:
    login(request, user)

Explanation:

  • Before: Calls authenticate and login without failure limits, delays, lockouts or CAPTCHA, allowing repeated password guessing.
  • After: Tracks failed attempts by account, IP address and device, applying a delay or lockout when a threshold is exceeded.

References