Skip to content

Instantly share code, notes, and snippets.

@benjaoming
Last active August 29, 2015 14:00
Show Gist options
  • Save benjaoming/11404561 to your computer and use it in GitHub Desktop.
Save benjaoming/11404561 to your computer and use it in GitHub Desktop.
Block brute force attempts with a Mixin intended for FormView
import logging
from django.core.cache import cache
from django.utils import timezone
from datetime import timedelta
logger = logging.getLogger('name-your-logger')
class BruteForceMixin():
"""
Use this in a FormView and call count_failure() in form_invalid
and reset_failure_counter() in form_valid
Source:
https://gist.github.com/benjaoming/11404561
Example:
class MyView(FormView, BruteForceMixin):
def form_valid(self, form):
self.reset_failure_counter()
super(...)
def form_invalid(self, form):
self.count_failure()
super(...)
Inspiration:
https://github.com/brutasse/django-ratelimit-backend/blob/master/ratelimitbackend/backends.py
"""
# Current max: 30 attempts in 15 minutes
brute_force_attempts = 30
brute_force_minutes_to_count = 15
def count_failure(self):
"""Protect against brute force by caching IP, timestamp and count"""
request = self.request
now = timezone.now()
counts = cache.get_many(self.block_cache_key_current())
if sum(counts.values()) >= self.brute_force_attempts:
logger.warning(
u"Brute Force rate-limit reached: PATH: {}, IP {} - POST DATA: \n\n{}".format(
request.path,
request.META['REMOTE_ADDR'],
str(self.request.POST)
)
)
raise RuntimeError('Rate-limit reached', counts)
cache_key = self.block_cache_key(now)
cache.set(
cache_key,
cache.get(cache_key, 0) + 1,
(self.brute_force_minutes_to_count + 1) * 60
)
def reset_failure_counter(self):
"""Reset all relevant counters"""
cache.delete_many(self.block_cache_key_current())
def block_cache_key(self, when):
"""Returns a single key"""
return 'brute-force-%s-%s' % (
self.request.META.get('REMOTE_ADDR', ''),
when.strftime('%Y%m%d%H%M'),
)
def block_cache_key_current(self):
"""Returns all keys matching the interval to look for (one per minute)"""
now = timezone.now()
return [
self.block_cache_key(
now - timedelta(minutes=minute),
) for minute in range(self.brute_force_minutes_to_count + 1)
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment