0

I'm trying to set the number of times a user can "try" passwords. Currently I want to store the number of times he/she could try passwords and the "penalty time" in sessions.

The question is will user closing the browser or changing another IP address have an affect on sessions?

For example, if the user has a penalty time for 5 minutes, which can be done by two datetime instances subtraction > 5 then let the user try 5 times again. If the user in the meantime closes and reopens the browser, will the "sessions" get lost?

Checks before logging the user in:

##############SESSION BASED##################
#Initialize tries, to be used later on
tries = "0"
try:
    tries = request.session['tries']
except:
    pass

#If tries > 5 times
if(int(tries) >= 5):
    timeNow = request.session['locked_time']
    timeDifferenceSeconds = (datetime.datetime.now() - datetime.datetime.strptime(timeNow, "%Y-%m-%d %H:%M:%S.%f")).total_seconds()

    #See if the difference is greater than 15 minutes, otherwise lock
    if(timeDifferenceSeconds > 900):
        request.session['tries'] = str(0)
        logger.info("User:" + str(username) + " is unlocked");
    else:
        logger.info("User:" + str(username) + " is currently locked");
        logger.info("User:" + str(username) + " returning Locked");
        return HttpResponse("Locked")
##############SESSION BASED##################

After the user gets a invalid login:

##############SESSION BASED##################
#if the user fails in providing the correct username/password, increment tries
try:
    tries = request.session['tries']
    num = int(tries) 
    num += 1
    tries = str(num)
    request.session['tries'] = str(tries)
except:
    tries = 0
    request.session['tries'] = str(tries)

#If tries > 5, then we will lock
if(int(tries) >= 5):
    logger.info("User:" + str(username) + " is not valid, current tries:" + str(tries) + " and will be locked");
    request.session['locked_time'] = str(datetime.datetime.now())
    logger.info("User:" + str(username) + " returning Locked");
    return HttpResponse("Locked")
else:
    logger.info("User:" + str(username) + " is not valid, current tries:" + str(tries));
    logger.info("User:" + str(username) + " returning Invalid");
    return HttpResponse("Invalid")
##############SESSION BASED##################

Currently I do not have SESSION_COOKIE_AGE changed, so it is currently by default 2 weeks.

Update:

Used a combination of IP and User Flags, haven't tested it, hopes it works

Before the user logs in, check for:

  1. Their IP addresses if they are in a "suspicious table", if accounts more than 10, then they cannot login
  2. Checks the number of tries, if it is greater than 5, then we lock them for 15 minutes multiplied the violations they have (the amount of times they went greater than 5 tries)

Code:

##############USER BASED AUTHENTICATION SYSTEM#####################
#Test for the user's public IP or best matched IP to see if it is banned
realIP = get_real_ip(request)
IP = get_ip(request)

#Get their realIP, and see if it matches, if they have over 10 accounts, then they are banned
try:
    suspiciousIP = suspicousIP.objects.get(pk=realIP)
    if(suspiciousIP.count > 10):
        logger.info("User:" + str(username) + " has a banned real IP")
        logger.info("User:" + str(username) + " returning Banned");
        return HttpResponse("Banned")
except:
    pass

#Get their IP, and see if it matches, if they have over 10 accounts, then they are banned
try:
    suspiciousIP = suspicousIP.objects.get(pk=IP)
    if(suspiciousIP.count > 10):
        logger.info("User:" + str(username) + " has a banned IP")
        logger.info("User:" + str(username) + " returning Banned");
        return HttpResponse("Banned")
except:
    pass

#Test the current user's blockedList
#2 conditions, if lockedTemp = True, then it is temporary locked until user has unlocked it
#by clicking the unlock email
#The other condition is whether tries > 5, and violations <= 5 (at 5th violation, the lockedTemp = true)
#then the user would need to wait until the time is over (900 seconds), which increases by each violation
userObject = None
try:
    userObject = blockedList.objects.get(pk=username)
    currentDateTime = datetime.datetime.now()
    objectDateTime = datetime.datetime.strptime(str(userObject.dateTime), "%Y-%m-%d %H:%M:%S.%f")
    lockedTemp = userObject.lockedTemp
    tries = userObject.tries
    violations = userObject.violations

    if(lockedTemp == "True"):
        logger.info("User:" + str(username) + " is temp locked");
        logger.info("User:" + str(username) + " returning tempLock");
        return HttpResponse("tempLock")
    elif (tries >= 5 and violations <= 5 and lockedTemp != "True"):
        timeDifferenceSeconds = (currentDateTime - objectDateTime).total_seconds()
        if(timeDifferenceSeconds > 900 * violations):
            userObject.tries = 0
            userObject.save(update_field=['tries'])
            logger.info("User:" + str(username) + " is unlocked, with tries:" + str(tries) + ", violations:" + str(violations))
        else:
            logger.info("User:" + str(username) + " is currently locked, with tries:" + str(tries) + ", violations:" + str(violations))
            logger.info("User:" + str(username) + " returning Locked");
            return HttpResponse("Locked")
except:
    pass
##############USER BASED AUTHENTICATION SYSTEM#####################

After they successfully logs in: Remove their blockedList and SuspiciousIP entries

#See if the user's remember me is checked, if it is not checked, then
#the session cookie will automatically expire when the browser closes
if(rememberMe == "true"):
    request.session.set_expiry(86400)
    logger.info("User:" + str(username) + " has set their expiration to 1 day")
else:
    request.session.set_expiry(0)
    logger.info("User:" + str(username) + " has set their expiration to expire when browser closes")

#See if the user is marked in blockedList, if true, then delete their row
try:
    userObject = blockedList.objects.get(pk=username)
    userObject.delete()

    logger.info("User:" + str(username) + " is in blockedList, removing their entry")
except:
    logger.info("User:" + str(username) + " is NOT in blockedList")
    pass

#See if the user's real IP is marked in suspicious IP, if true, then remove their entry
try:
    suspiciousIP = suspicousIP.objects.get(pk=realIP)
    suspiciousIP.delete()

    logger.info("User:" + str(username) + " is in suspicious real IP, removing their entry")
except:
    pass

#See if the user's IP is marked in suspicious IP, if true, then remove their entry
try:
    suspiciousIP = suspicousIP.objects.get(pk=IP)
    suspiciousIP.delete()

    logger.info("User:" + str(username) + " is in suspicious IP, removing their entry")
except:
    pass

If they have an invalid log in:

  1. Increment tries, and if tries > 5, then they are locked, the "time testing" their locks on dateTime will occur before login
  2. If they have more than 5 violations, then the account will be locked until a user unlocks it by clicking a link inside an automatic generated email sent to the us

Code:

##############USER BASED AUTHENTICATION SYSTEM#####################
try:
    #Get their current object, if exists, and increase their tries
    userObject = blockedList.objects.get(pk=username)
    userObject.tries += 1

    #If their tries >= 5, then lock them temporary, and increase violation
    if(userObject.tries >= 5):
        logger.info("User:" + str(username) + " is not valid, current tries:" + str(userObject.tries) + " and will be locked");
        userObject.violation += 1
        userObject.dateTime = str(dateTime.dateTime.now())

        #If violation >= 5, then we will tempLock, and can only be unlocked by email
        if(userObject.violation >= 5):
            logger.info("User:" + str(username) + " is not valid, will get TempLocked");
            userObject.lockedTemp = "True"
            userObject.save()

            #Get their suspicious Real IPs, and increase them, or make a new one
            try:
                suspiciousIP = suspicousIP.objects.get(pk=realIP)
                suspiciousIP.count += 1
                suspiciousIP.save()
                logger.info("User:" + str(username) + " has a suspeciousIP:" + str(suspiciousIP) + " current count:" + str(suspiciousIP.count));
            except:
                if realIP is not None:
                    newSuspiciousIP = suspicousIP(IP = realIP)
                    newSuspiciousIP.save()
                    logger.info("User:" + str(username) + " has a new suspeciousIP:" + str(realIP));

            #Get their suspicious IPs, and increase them, or make a new one
            try:
                suspiciousIP = suspicousIP.objects.get(pk=IP)
                suspiciousIP.count += 1
                suspiciousIP.save()
                logger.info("User:" + str(username) + " has a suspeciousIP:" + str(suspiciousIP) + " current count:" + str(suspiciousIP.count));
            except:
                if IP is not None:
                    newSuspiciousIP = suspicousIP(IP = IP)
                    newSuspiciousIP.save()
                    logger.info("User:" + str(username) + " has a new suspeciousIP:" + str(IP));

            logger.info("User:" + str(username) + " returning tempLock");
            logger.info("User:" + str(username) + " returning tempLock");
            return HttpResponse("tempLock")

    userObject.save()
    logger.info("User:" + str(username) + " returning Locked");
    return HttpResponse("Locked")
except:
    newUsername = username
    newRealIP = realIP
    newIP = IP
    newDateTime = ""
    newTries = 1
    newViolations = 0
    newLockedTemp = "False"

    newUserObject = blockedList(username=newUsername, realIP=newRealIP, IP = newIP,
                                dateTime = newDateTime, tries = newTries, violations = newViolations,
                                lockedTemp = newLockedTemp)
    newUserObject.save()

    logger.info("User:" + str(username) + " returning Invalid");
    return HttpResponse("Invalid")
##############USER BASED AUTHENTICATION SYSTEM#####################

Extra Models that are used:

class blockedList(models.Model):
    username = models.CharField(max_length=200, primary_key=True)
    realIP = models.CharField(max_length=200)
    IP = models.CharField(max_length=200)
    dateTime = models.CharField(max_length=200)
    tries = models.IntegerField()
    violations = models.IntegerField()
    lockedTemp = models.CharField(max_length=200)

class suspicousIP(models.Model):
    IP = models.CharField(max_length=200, primary_key=True)
    count = models.IntegerField()
user1157751
  • 2,427
  • 6
  • 42
  • 73
  • 2
    by default session is stored in client side. User can delete session cleaning cookies browser. Another approach: http://stackoverflow.com/questions/9033287/lock-out-users-after-too-many-failed-login-attempts – dani herrera Sep 08 '14 at 06:24

1 Answers1

10

As you mentioned in your question, sessions in Django live for as long as SESSION_COOKIE_AGE determines (which defaults to 2 weeks) from the last time it was "accessed".

Two exceptions for that:

  • you can set an expiry time to a session yourself, and then it depends on that.
  • the settings SESSION_EXPIRE_AT_BROWSER_CLOSE is set manually to True. In that case each time the user closes the browser the session clears

But it is very important to understand that session ids are stored in cookies on the client side, so it is very easy for a user to just delete its cookies and then the Django server will consider this a new session.

Another way of implementing what you want is to save this data on the user - meaning if, after a few erroneous attempts to log in as user user@example.com, you can just flag that username as banned for X minutes. You'll need a different database table to save that information and your own logic. I don't currently know of an app that does that, but a quick search might prove me wrong.

That way, even if the user clears his sessions, the server won't allow login for the user.

Gabriel Amram
  • 2,700
  • 2
  • 19
  • 29
  • Thanks for the answer. I think my best bet is to save the client's IP, and do a ban for X minute. Plus if this violation occurs more than Y times (getting banned 3 times in a row in Z number of minutes), then lock account and sends a unlock email. – user1157751 Sep 08 '14 at 07:48
  • If you are afraid that someone tries to hack into a specific account, then he can just change IP addresses. So I guess that's not enough. If it is someone that just tries to gain access to ANY account then you'll need an IPS/IDS mechanism that can detect such behavior, and that is much more complicated – Gabriel Amram Sep 08 '14 at 07:50
  • I think an IDS is an overkill currently. Wouldn't locking an account permanently suffice after X tries until the "real" user goes to his/her mail and click on the unlock mail? – user1157751 Sep 08 '14 at 07:55
  • Just to think about it, the constant attacks might be very annoying since the user keeps getting locked out. – user1157751 Sep 08 '14 at 07:56
  • I agree that the IPS/IDS is a total overkill. But the point was that flagging the client IP is not enough if someone tries to break in to a certain account. A hacker will find a way to bypass that easily. I think that flagging the username is a better approach – Gabriel Amram Sep 08 '14 at 08:03
  • Well, I've updated my code, so I used a combination of IP and User flags. – user1157751 Sep 08 '14 at 22:28
  • @GabrielAmram "sessions in Django live for ... 2 weeks from the last time it was "accessed"" - are you sure this is the case? I did some tests with custom SESSION_COOKIE_AGE, seems it's calculated from the time it was _set_, not from the time it was last accessed. Checked the docs, saw nothing about described behaviour too. – egor83 May 13 '22 at 16:01
  • 1
    Session cookies _do_ get their expiry date updated, though not on access, but on writing: "Similarly, the expires part of a session cookie is updated each time the session cookie is sent." https://docs.djangoproject.com/en/4.0/topics/http/sessions/#when-sessions-are-saved – egor83 May 13 '22 at 19:20