Last active
August 29, 2015 14:00
-
-
Save benjaoming/11404561 to your computer and use it in GitHub Desktop.
Block brute force attempts with a Mixin intended for FormView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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