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.
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
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
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)