37

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.

Partack
  • 886
  • 11
  • 24
  • 14
    I just have to say wow at the diagrams! – rid Aug 03 '11 at 15:14
  • @Radu - is that a bad 'wow'? .. I was just trying to be as concise as possible.. – Partack Aug 03 '11 at 15:16
  • 9
    @Patrick, it's great! I just wish more people would work like you! If only all the projects that I hopped into had these kinds of diagrams explaining what's going on, my life would've been totally different. – rid Aug 03 '11 at 15:17
  • @Radu - Thank you for your generous compliment =) I've a strong belief that pictures usually explain problems better than I can describe them. haha – Partack Aug 03 '11 at 15:22
  • Is your website used by mobile phones? Because they may have different IP addresses very easily during one session, e.g. multiple close WiFi-spots – Pindatjuh Aug 03 '11 at 15:26
  • @ Pindatjuh - It may be. The style of website would be somewhat of a (Simplistic) 'free ads' website. Users that are not logged in will be able to browse the ads but not post one unless they have an account. I think I want to scrap the use of IP addresses in the MD5 hash because I think I would like users to be logged in from multiple places. (probably less secure, but it's a common behavior these days and it's more convenient..) – Partack Aug 03 '11 at 15:31
  • 4
    @Patrick, as a general rule, whenever you're thinking of using MD5, use SHA1 instead. [MD5 is broken](http://www.google.ro/search?aq=f&sourceid=chrome&ie=UTF-8&q=md5+is+broken). – rid Aug 03 '11 at 15:34
  • @Radu - (Quote from wikipedia:) US-CERT says MD5 "should be considered cryptographically broken and unsuitable for further use," and most U.S. government applications now require the SHA-2 family of hash functions. - Wow. Thank you for letting me know. I'll use SHA1 from now on for sensitive/critical data. – Partack Aug 03 '11 at 15:38
  • @Radu,Partack - The hash scheme argument is irrelevant for this case: loginToken = hash($salt.$username...). The core requirement is that the token have sufficient randomness that a brute force attack will be impossible. Either algorithm will be fine assuming sufficient salt randomness, and conversely either will fail if the salt sucks. An a related note, do not use a pseudo random number generator to construct salts (e.g. mt_rand()). I prefer reading from /dev/urandom instead as a ready source of entropy available on *nix systems... not sure what Windows people do though. – Chris Aug 03 '11 at 16:13
  • @Chris, I'm just saying that MD5 should be avoided in general, nothing specific to this. Also, Windows people should really not run PHP on their servers... It's suboptimal at best. – rid Aug 03 '11 at 16:17
  • 1
    Also, you make like to add a timeout period to the loginToken. So maybe the whole login cookie could be something like this: { uid: #, token: "eiurldkjo2834...", valid_until: "Aug 4 2011" }. Also also: if the user logs out, invalidate the token. This will have the effect of logging them out on all systems everywhere. When a user has their laptop stolen they will appreciate this feature. – Chris Aug 03 '11 at 16:20
  • 2
    @Radu MD5 is still commonly used for some hashing problems with a small enough locus of data points (e.g. choosing memcached keys). For one-way hashing of for example passwords both md5 and sha1 are inadequate because the algorithms are too fast. Here is a discussion that I found useful: http://stackoverflow.com/questions/2235158/php-sha1-vs-md5-vs-sha256-which-to-use-for-a-php-login – Chris Aug 03 '11 at 16:31
  • 1
    Also check out [this question](http://crypto.stackexchange.com/questions/305/is-https-secure-if-someone-snoops-the-initial-handshake) about the security of HTTPS. – rid Aug 03 '11 at 19:05

3 Answers3

8

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
rid
  • 61,078
  • 31
  • 152
  • 193
  • So an attacker can not make a new session with different contents? for example if i'm simply storing the username in the session and using that to prove they're logged in, an attacker could not change the username in that session to 'admin' instead and wreak havoc on my website? they could only hijack a currently logged in user (on the same network) (for instance using [Firesheep] (http://codebutler.com/firesheep) ) ? – Partack Aug 03 '11 at 15:49
  • 6
    @Patrick, no, nobody other than the administrator of the server can change the contents of a session. The session is stored on the server. The client has the *session ID* which it then sends the server. The server is then responsible to fetch the data identified by that ID from its local storage and do whatever it needs to do with it. Firesheep steals session IDs so they can send the server the same session ID as the legitimate user. However, if all the data is sent via HTTPS, then the session ID will be unreadable by anyone other than the parties participating in the HTTPS session. – rid Aug 03 '11 at 15:51
  • I like this one. Everything in an http communication can be spoofed, except the ip address. Unfortunately, the most likely case of someone stealing cookies and reusing them on your site involves people sharing a router (e.g. at a Starbucks hotspot, see Firesheep) and they will also be sharing the ip of the shared router. As for the cons, you can choose to ignore this concern until you develop some following and then switch to https (see Facebook and every other large site in existence today). – Chris Aug 03 '11 at 15:56
  • @Radu - Your comment illustrates how a session works, perfectly. Thank you very much! – Partack Aug 03 '11 at 15:59
  • @Chris - So how would one go about spoofing the contents of a session if it is stored on the server? or is this not what you were implying in your statement 'everything in a http communication can be spoofed except the IP address' ? Do you perhaps know of any articles on this topic? I'll do some more googling if not. It would be good to know if this is true but seems highly unlikely if the content is stored on the server. – Partack Aug 03 '11 at 16:03
  • 3
    @Partack, the contents of the session is not part of an HTTP communication. The session ID is sent over HTTP, not the session contents. The HTTP communication will contain the request (GET, POST, etc and the URL), headers (cookies, Accept-Encoding, etc) and data in the response or the POST request. Take a look at the [HTTP page on Wikipedia](http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol) to see what an HTTP request and response looks like. – rid Aug 03 '11 at 16:10
  • @Partack What Radu said. You should familiarize yourself with the actual vulnerability as it sounds like you have read more into it than is actually there. Download Firesheep and try it out for yourself. You can hack yourself using different browsers if there are no other targets available on your subnet. – Chris Aug 03 '11 at 16:35
  • 1
    @Radu As a noob, your first comment here really elaborated on your answer and helped me understand session saving even better. Perhaps it would be good to add the contents of your comment to the actual answer itself? – Phil Aug 04 '11 at 14:10
  • Single host SSL certificates are cheap. Really. It's only when you get into hosting (serving many sites off the same machine) that the costs start to ramp up, and the same also applies to the computation and network overheads of HTTPS. – Donal Fellows Aug 05 '11 at 10:25
4

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

Flambino
  • 18,507
  • 2
  • 39
  • 58
  • A very clear and thorough answer, thank you =). Although I already knew some of your points, I'm still very happy that you included them just in case there was anything that I was not aware of. – Partack Aug 03 '11 at 17:16
3

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.

Wrikken
  • 69,272
  • 8
  • 97
  • 136
  • Thank you for your swift response, your suggestion to block IP's that have too many invalid cookies in a short time was the most useful to me and I appreciate your other points also. =) – Partack Aug 03 '11 at 17:19
  • after deeper thought into the blocking IPs after too many invalid cookies thing, i thought that this would be a bad idea because if a users cookies were being hijacked , blocking their IP address would lock the user out too. So it might just be a better idea to log them out with a warning that somebody on their network might be doing something dodgy and advise them that if they are on a public network, they should connect elsewhere.. maybe. I'm not entirely sure how to handle such an event but I know that blocking their IP would be a bad thing.. – Partack Aug 04 '11 at 20:44
  • as another example, blocking their IP would probably take out an entire public lan if more than one legitimate user were using the site at once (or even not all at once but that network would be blocked for later use). just a thought. – Partack Aug 04 '11 at 20:45
  • Well, I worded it badly. I'd never really _block_ them, I'd just temporarily disable logging in by cookie for that IP... Indeed, relogins seems best (or not keep-me-logged-in feature at all, I've always hated those). – Wrikken Aug 05 '11 at 09:00
  • Fair enough. Your point that something should happen when this event occurs is still definitely a valid one so that's the important thing. =) – Partack Aug 05 '11 at 14:00