A couple of days ago on reddit.com's /r/netsec a poster by the name of Dan Weber posted what he believed to be an attack on PHP sessions: Hacking PHP sessions by running out of memory (reddit link). The way the "attack" works is as follows:
- Create a new session
- Assign some new data to the session, in this case a username which is used to signify that the user is logged in.
- Check to verify that the user should be logged in
- If the user should not be logged in, destroy the session.
The "attack" would be to run the PHP script out of memory on number 3, since once something is set on the session it is immediately stored, so even if the user is not supposed to be logged in, they are now logged in since their session says they are.
I wouldn't necessarily call this a PHP hack, this is really just bad practice in terms of programming, the logic should be reversed.
- Create a new session
- Verify the user should be logged in
- If so, set the session data
- If the user should not be logged in, don't set the session data
That would solve the problem at hand, and now there is no way for the user to trick the PHP script into believing she is logged in when that is not the case.
However as the discussion went on on Reddit, it became even more clear that there are no good resources on what you should store in the user session, and what you shouldn't store in the user session. Some of these things may seem like common knowledge, but sadly this is something every single new person to programming has to learn on their own.
Let's get this out of the way, this is in no way limited to PHP, but it is the one I will be using as an example. This can all apply to Ruby (Ruby on Rails), Python (Pyramid) or many other frameworks.
The basic problem is that generally writing to the session is not an atomic transaction based on the page accessed, so the assumption made in this article is that when you write to the session it is instantly committed, and there is no way to roll it back upon failure. If there was, our first example listed wouldn't be able to occur since upon running out of memory at step 3, the session would have been rolled back and cleaned up.
What should you store in the users session?
You should only really store anything in the session that if it were made public it would do very little harm, or if an attacker gained access to the cookie associated with that session you could easily expire that particular cookie and the associated information.
Really the list of items to be stored in a session that is tied to a cookie is as follows:
- The users unique id
- A random unique number that is unique across all sessions
- Temporary state (i.e. Flash messages)
More importantly, don't store permission bits, or group memberships, or anything that is used to allow/deny access to particular resources.
Why is this important?
One of the things that Dan Weber brought up in the Reddit post was storing the users permission level and group membership in the session. If your code is then relying on the session to always contain the right permission level, then there is no way to expire someones access to the data.
- A user logs in
- A user gets various permissions, and they are set in the session
- The user has been fired from his job, and an administrator removes the users permissions
- Since the user is still logged in, the users permission bits are still set, and he continues to have access to parts of the site/information he shouldn't have access to.
If instead on every page visit we simply pull out the users unique id and verify the permissions upon access, as soon as the permissions are revoked by the administrator the user no longer has access to the various resources.
Expiring sessions (Why the random number that is unique?)
There should be a way to aggressively expire sessions, before cookie expiration. This way even after the users permissions are changed, it is possible to force the expiration of the session so that the user is required to re-authenticate.
The random unique number should be stored server side. When the client comes back, with a session that already contains a user id, you verify that the random unique number is valid for that particular user, and verify that the random unique number still exists server side.
This would allow an administrator for example to see all sessions that are currently associated with a particular user, and force expire them, thereby essentially logging that particular session out. This could also be used to implement various log-in requirements, such as only two sessions are allowed to be active at any one time.
There has to be an easy way to remember something from page visit to page visit that isn't considered detrimental if the information gets lost. One of those things is flash messages. Flash messages are generally used to provide the user indication that something has changed, they are shown once and then never again.
Storing these as session data makes sense. If the flash message gets set, great, if it doesn't get set, it doesn't matter. Flash messages are simply a notification tool, if the user misses them it isn't important.
What you shouldn't store in the session
Definitely don't store any kind of permission bits, groups a user is a part of or anything that would allow the user access that they normally would not be able to access.
On each page access check what permissions the user has. While it may mean a little more heavy lifting server side it provides extra security, and the means to enforce changes in permissions instantly.
Good secure programming practices
Keep secure programming practices in mind at all times, always consider how the information you are storing/processing is accessed/viewed/administered. More importantly think about the access controls that are in place, and how one could expire access to a particular resource without requiring a co-operative client. Remember, just because you tell an client (attacker) to expire a cookie, doesn't mean they have to comply.
The ordering of how variables are set, and when they are set are very
$_SESSION['isadmin'] = True at the top of a PHP script, and then
removing it by checking to see if the user is actually an administrator later
on in the script is a bad idea.
Always order your code so that if a failure does occur there is no chance that a critical section of your code is executed by accident, or that information is stored in a half-verified state. This is especially important for access control.