Home » Php » The dreaded 'keep me logged in' and session checking

The dreaded 'keep me logged in' and session checking

Posted by: admin November 30, 2017 Leave a comment

Questions:

So, I know this has been done to death but all the answers I’ve come across have been extremely confusing/contradicting each other or their explanations have been incomplete and I’m trying to keep up and do this my self using all the resources available but I think I’ve gotten lost somewhere. I would like to clarify this once and for all. Thank you for your patience in advance as this may end up being a little bit long winded.

I have a small login box at the top of my page which will remain constant if the user is not logged in. If they are logged in, then instead of a login box, they will see a greeting with their name in it.

Session Checking

So first of all, here is a diagram of (to my understanding thus far) how to check the user can access ‘members only’ content. (this code goes at the top of the page to check and set variables such as $loggedin = true;)

Session Checking Diagram

As it stands, my $_SESSION['loggedin'] is just the users name. It’s to my understanding that sessions can be faked or hijacked from the same domain and so I know this is very insecure (for instance an attacker could somehow make a session containing a different users name and voilà – instant access to that users stuff) But I don’t know how I should be checking the session. The only way I can imagine to do this is to connect to the database every time a page is loaded and check an MD5 hash or something from the database (And renew it) but I imagine this would generate a lot of needless server traffic and i’m almost sure there’s a better way to do it.

Logging in

Here is a diagram of what happens when a user logs in (And whether to display the greeting or the login box.)

Logging In Diagram

For the most part i’m pretty solid on this part (I hope) but I don’t know what my MD5 hash should contain in order to be able to later re-check the hash with the one in the database, the one in the cookie and a newly generated hash to make sure a cookie hasn’t been conjured by an attacker.. Also, as stated in the comments below, I’m probably going to scrap the use of IP address in the hash to allow for users to stay logged in from multiple locations (for instance, their phone and their laptop.)

So my questions are:

  • how should I check my sessions are not fake?
  • how should I check my cookies are not fake?
  • would my log in method be secure enough after the checking is in place?
  • is there anything important I have left out?

If there is anything you would like to ask, please let me know in a comment and I will be happy to edit my question with as much information as I can provide.

Answers:

You cannot hijack the contents of a session. If you’re the man in the middle, then you can act as the user by using the same session ID. This problem however is still valid for any kind of session.

If you want to prevent the man in the middle attack, you should use HTTPS.

Using HTTPS guarantees that:

  • your sessions are not fake, because there is practically no way an attacker can steal someone’s session ID and use it as their own
  • your cookies are not fake, for the same reason
  • your log in method is secure, because everything is sent and received encrypted, which would make it practically impossible for an attacker to either see, modify or intercept the data sent and received from the server

The main disadvantages of HTTPS are:

  • you will need to purchase a certificate
  • encryption will add some overhead, both in network traffic and in resource usage
  • data sent via HTTPS will not be cached
Questions:
Answers:

Very nicely illustrated question!

The thing to note about PHP’s session support, is that it too relies on cookies. PHP automatically sets a cookie with a session-ID that PHP assigns. It then automatically reads that cookie and loads the session.

Now, cookies can be hijacked (see Firesheep for example) by “man in the middle attacks”. The attacker simply copies another person’s cookies (which are sent along with every request), and adds them to his own browser. And since sessions are identified via cookies, you can hijack a session in this way (the attacker will simply be use the site with the very same session as the user). Or you can hijack the “remember me” cookie for later use.

The only real safeguard against such hijackings is to have an encrypted connection, i.e. “https” connection. Then, the cookie data sent back and forth will be scrambled for every request, so someone else can’t copy its actual content.

Otherwise, you’ve got the right idea for how to match a persistent-login cookie to a user (i.e. hash in the cookie must match that in the database). For the session checking, you don’t need any hashing, as the session data itself never leaves the server. So there you can just set $_SESSION['logged_in_user_id'] to the UID – or to false/null, if they aren’t logged in. That is, if the cookie checks out, or the username/password checks out, then set the user id in the session as a simple int.

As for the “manual” log in, where the user sends his/her username and password, you’re again exposed to man in the middle attack. As before, it’s simply a matter of intercepting the data being sent to the server. Unless you use an encrypted connection, the cookies and the username/password (and everything else) will all be cleartext and plainly readable to an attacker.

Minor points:
For the hashing algorithm, I’d go with sha1 rather than the older md5. And for the username/password in the database, it’s never a good idea to store the password as cleartext in the database. Should your database get breached, the attackers could just read everything there regardless of how securely your server otherwise communicates with your users.

You probably know all this already, but just in case: For the users table, store the username as cleartext, but also generate a salt (say, hash the current time plus a random – but constant! – string you define, and hash it repeatedly several times). Store the salt in the database, and use it when hashing the password. And again, hash it repeatedly many times. Finally, store the hashed password in the DB. You won’t be able to send people their passwords, because you don’t know either (you just know the hash), but you can implement a “reset password” option, which generates a new salt, and a random string, which you then hash like any other password. Just remember to keep the new un-hashed password around long enough to send to the user. The reason you want to hash it several times, is that it makes it makes it unfeasible to use a rainbow table to “reverse” the hashing, even if you know the salt(s). If you just hashed a password once, with md5 and no salt, it’d be trivial to look up the hash and see what the unhashed password is (unless your users are all smart enough to use really long, random passwords which aren’t found in any rainbow tables).

Long story short: The session, cookies, and username/password are all vulnerable to the same kind of attack. The most practical safeguard against it is using SSL (since, as you note, IP addresses change).

Also see the other answers. The more info the better 🙂

Questions:
Answers:

how should I check my sessions are not fake?

By setting a variable on creation.

session_start();
if(empty($_SESSION) || !isset($_SESSION['notfake']){
   $_SESSION = array();
   session_regenerate_id();
   $_SESSION['notfake']=1;
}

how should I check my cookies are not fake?

By checking them in the database. Have a reasonably long identifier. Not much else you can do, besides blocking IPs that have ‘to much’ non-valid cookies in a short time.

would my log in method be secure enough after the checking is in place?

Depends, you do use HTTPS for everything? Also, set the session-cookie to https-only.

is there anything important I have left out?

For important actions (changing password/email/etc.), require the user to resupply their password. Also, don’t use a plain hash, add some fixed string (= salt) to the values prior to hashing to avoid the direct use of rainbow tables.