Here is a very simple mechanism for wrapping a decorator around your views to protect them against brute force attempts. For instance, if you have a secret file download available only with the right secret (/view/id/secret-hash/), you expose your view to simple brute force attempts.
Simply put, this decorator will log a 404 response object or Http404 exception, count pr. IP and return status=400 and send you an email whenever a new block is put into place.
Model
class IllegalLookup(LogModifications): """Log and block illegal lookups""" created = models.DateTimeField( verbose_name=_(u'created'), auto_now_add = True, ) modified = models.DateTimeField( verbose_name=_(u'created'), auto_now = True, ) ip_address = models.CharField( max_length=16, null=True, blank=True, verbose_name=_(u'IP address'), ) path = models.CharField( max_length=255, null=True, blank=True, verbose_name=_(u'path'), help_text=_(u'First attempted path is always logged'), ) count = models.PositiveIntegerField( default=1, ) @classmethod def log_lookup(cls, ip_address, path): try: now = timezone.now() expired = now - timedelta(minutes=settings.BLOCK_EXPIRY) lookup = cls.objects.get(ip_address=ip_address, modified__gte=expired) lookup.count += 1 lookup.save() except cls.DoesNotExist: # Delete old entries first cls.objects.filter(ip_address=ip_address).delete() lookup = cls.objects.create(ip_address=ip_address, path=path) lookup.save() @classmethod def is_blocked(cls, ip_address): try: now = timezone.now() expired = now - timedelta(minutes=settings.BLOCK_EXPIRY) lookup = cls.objects.get(ip_address=ip_address, modified__gte=expired) if lookup.count == settings.BLOCK_ATTEMPTS: mail_admins("IP blocked", "{0} is now blocked, IllegalLookup id: {1}".format(ip_address, lookup.id)) if lookup.count > settings.BLOCK_ATTEMPTS: return True except cls.DoesNotExist: pass return False |
Decorator
def log_and_block(func): def _log_and_block(request, *args, **kwargs): remote_ip = request.META.get('REMOTE_ADDR', None) if IllegalLookup.is_blocked(remote_ip): return HttpResponse('%s is blocked' % remote_ip, status=400) is_404 = False is_exception = False try: return_object = func(request, *args, **kwargs) if return_object.status_code == 404: is_404 = True except Http404: is_404 = True is_exception = True if is_404: if remote_ip: IllegalLookup.log_lookup(remote_ip, request.META.get('PATH_INFO', '')) if is_exception: raise return return_object return _log_and_block |
Usage
Now, simply wrap the decorator around your view:
@log_and_block def my_view(request, id, secret_hash): object = get_object_or_404(models.MyModel, id=id, secret_hash=secret_hash) |


