- Joined
- Feb 15, 2007
- Messages
- 2,340
- Reaction score
- 653
Preamble
So we all know them, and we love them. Sessions are awesome. Why? They let us pretend our stateless protocol is stateful! Awesome, now we can write truly interactive programs.
But... in light of
As a precursor, if you haven't heard of OWASP, but you're writing public-facing web applications, you may be a fool. You should check out their guidelines to developing securely, in particular, here's the one on sessions:
In that document, the outlined "things to do" is one of the key issues, "If applications use both HTTP and HTTPS, they MUST regenerate the identifier or use an additional session identifier for HTTPS communications." This is key, and not doing this makes HTTPS somewhat useless as session hijacking can be used to gain sensitive information.
I want to point out before I get started that I am NOT, again NOT implementing my own session management API here, and nor should you. PHP's available session management api is sufficient to properly secure applications providing sessions. You can emulate this behavior with your own code using cookies and files or database entries but YOU DON'T NEED TO. In fact, you can add these capabilities on top of the existing session management API quite easily, so don't re-invent the wheel here.
Introduction
So let's start where the tutorials leave off. A quick google for "
The list goes on. Do you see a pattern here? Most of these pages aren't really tutorials at all, rather just an explanation of how to use a single PHP function. PHP F1 starts off decently explaining some of the information
Let's start with the basics that nobody really explains very well. HTTP is at its most basic, most essential (insofar as you care about as a developer) is just a plaintext protocol with 2 commands: GET and POST. GET requests a specific file on the webserver, to which the webserver responds to by dumping the contents of the file (and mime information, blah blah blah) back to the client, or one of various error codes indicating some error. In the case that we have a scripting language tied into this processing stack, be it ASP, PERL, PHP, or something else, this GET usually causes your script to be executed and the output of it to be sent over instead of the file itself. The POST command sends data to the server along with the request, which your script then gets through the scripting environment.
Notice we don't talk about state management. That's because state doesn't exist in HTTP. State is loosely implemented with cookies. An example cookie header sent to the client over HTTP is:
(This example was taken from the wiki page on
Now notice the server tells the client to set a cookie with name 'name' to value 'value'. Now when the client sends a request to the server, it will send along a cookie:
This is highly over-simplified, as cookies have various other attributes such as an expiration and an option that determines whether the cookie should be sent over an HTTPS connection only or not, but we really only need to understand the basics.
So you see how you could say.. send a cookie to the client call it 'PHPSESSID' with some long unique string identifying a client, then create a file with that same name in some directory on the server, call it "C:\Sessions\" or "/var/sessions". For example, we send "PHPSESSID=30EF88B27A7AA09F" and I create files "C:\Sessions\30EF88B27A7AA09F.txt" or "/var/sessions/30EF88B27A7AA09F.txt".
Once this session is set up, every time a page request comes in, I read the cookies sent by the client, and when I find 'PHPSESSID' I look in "C:\Sessions" for that file. If it exists, I load it and pull the information out and populate some kind of an array. At the end of my page, I just need to save this array back to that file. And now you can see how I can store information about a specific client (up to the unique session ID) over the lifetime of their use of my application, called their "session."
If you're familiar with how HTTP works, you'll know the query string can also be used to send information from the client to the server. That's how GET variables work, for instance if you access a page like:
Everything following the question mark, the part in bold, is the querystring of the URL entered. It's a list of ampersand separated variables (much like cookies except passed publicly and explicitly rather than privately and implicitly). You can also establish a session in this manner but it is less common and not recommended unless absolutely necessary (this is truly rare).
Sessions in PHP
Anyway, back to the topic. As you can see, the concept of establishing a session is not too difficult to understand. You also understand that at some point you want to delete their session file and if you have sensitive information there, you may want to erase it when they log-out.
This is the most basic session management you can implement. The function session_start in PHP explicitly creates a session for the client making the page request IF ONE DOES NOT ALREADY EXIST, then it fills the superglobal array $_SESSION with variables that exist in the person's session. By default it grabs the cookie 'PHPSESSID' and pulls the person's session information from a session file. You can visualize this as:
How PHP handles these cookies and session information can be modified by several php.ini settings. They are outlined
session.use_cookies - session.use_cookies specifies whether the module will use cookies to store the session id on the client side. Defaults to 1 (enabled).
session.use_only_cookies - session.use_only_cookies specifies whether the module will only use cookies to store the session id on the client side. Enabling this setting prevents attacks involved passing session ids in URLs. This setting was added in PHP 4.3.0. Defaults to 1 (enabled) since PHP 5.3.0.
session.cookie_lifetime - session.cookie_lifetime specifies the lifetime of the cookie in seconds which is sent to the browser. The value 0 means "until the browser is closed." Defaults to 0. See also session_get_cookie_params() and session_set_cookie_params().
There is a note here in the official documentation letting you know that this time is set relative to the server's time. That is because when setting a cookie's lifetime (even using the cookie API) the timeout is offset from the server's time. If this doesn't match the client's time, weird things can happen.
session.cookie_secure - session.cookie_secure specifies whether cookies should only be sent over secure connections. Defaults to off. This setting was added in PHP 4.0.4. See also session_get_cookie_params() and session_set_cookie_params().
session.cookie_httponly - Marks the cookie as accessible only through the HTTP protocol. This means that the cookie won't be accessible by scripting languages, such as JavaScript. This setting can effectively help to reduce identity theft through XSS attacks (although it is not supported by all browsers).
session.name - session.name specifies the name of the session which is used as cookie name. It should only contain alphanumeric characters. Defaults to PHPSESSID. See also session_name().
and finally
session.save_handler - session.save_handler defines the name of the handler which is used for storing and retrieving data associated with a session. Defaults to files. Note that individual extensions may register their own save_handlers; registered handlers can be obtained on a per-installation basis by referring to phpinfo(). See also session_set_save_handler().
So notice just looking at these configurable settings for session handling that we're referred to some dynamic session API that you may not have been aware of. Notably, session_get_cookie_params and session_set_cookie_params. We'll look into using these and a few other session management API a little later on to implement sessions properly and in accordance to some OWASP best practices.
Let's go back to what people usually do. So far we've seen some basic tutorials on "how to use sessions." This will probably get you with some pages like this:
Now this doesn't do a lot, but it works, right? Now you may add some log-out capability with session_destroy. But have you read what that API does from the documentation? No? Let me get that for you:
WHAT? Oh well that's fine right? We delete the data so we should be fine. No. We also want to regenerate the session key. But we wan't to do this safely.
This is where "SSS" exploits and long-lived session hijacks can creep in. It also lends itself to session collision which is a terribly annoying thing to deal with.
When I talk about "SSS" I really mean session collision. But I mean it in a way that can accidentally elevate privileges in another web application. So before discussing this, let's start with what session collision is.
Let's say you have developed some web application, let's call it A. This is hosted at
Now let's say you're working on another application on the same website. Maybe a bulletin board system for your users to get help. Call it B, and it's hosted at
You don't really run into this until you try to run multiple applications under the same domain. This is because cookies are domain-specific. If you're using the same webserver but different virtual servers based on domain, this isn't an issue. But the other security concerns are.
The next concern is session hijacking and information leakage and/or impersonation. Applications like Firesheep do what we could already do, but make it REALLY easy. When an HTTP request goes over a wireless connection, it's in plaintext. But on an unencrypted wireless network, everyone's packets are transmitted aloud via the radio in their computer (this is how wireless works). So other people can listen in to what you're doing. Since these pages are over HTTP, they're sent plaintext, so you can easily enough pull out the cookies being sent to the server, and in this case, specifically the PHPSESSID cookie. Now you just have to modify your browser's cookies with a little bit of javascript code to modify your own PHPSESSID to theirs and you can impersonate them.
Websites that use HTTPS for authentication only are not immune. Yes, the HTTPS protects the user's password from being sent in the clear, but that's not too important. Even if the website sits in HTTPS *USUALLY* but, for some things, perhaps AJAX requests (for efficiency) uses HTTP, if the website isn't secure, you can grab the user's PHPSESSID from these requests as well and through impersonation, gain access to these HTTPS pages and see all of the same information.
The real solution is what is outlined in the OWASP (Open Web Application Security Project) guidelines. Sessions should be regenerated often, depending on how sensitive the application is. Sessions should always, ALWAYS be regenerated when a user logs out of an application. And a new HTTPS ONLY session should be generated on the first HTTPS request and, optionally, tied to the HTTP session for user verification purposes. Further, it's best to also tie in more information about a user to verify the validity of the session identifier. For instance, the IP address of the user, the browser's user agent, and potentially the client's SSL certificate. For security purposes, it's also useful to log instances where invalid sessions are seen or brute-force attempts to use invalid sessions appear (this can be done with a little session-level counting and then a call to trigger_error). In addition, my own note, is that you should also use custom named sessions to segregate session handling for each web application, so that session collision cannot happen.
So on to the session API you probably didn't know about and that we can use to implement these requirements in a nice little session class. I'm not handling caching and sessions in this tutorial but I may in a later one. It's an important feature of PHP to understand and utilize to squeak out as much performance as possible from your applications.
This function will return an array of cookie parameters. The array returned includes the following keys:
Of these, we really only care about lifetime and secure.
We can use this to implement application-specific session names to avoid collisions.
This one is just awesome. We'll use this to regenerate sessions on log-in or privilege change and periodically for high-risk applications.
We will use this sparingly so I want to make sure you're aware of it.
I want to introduce this mainly to show how easy it would be to implement MySQL-based sessions, for instance. You can easily extend the Session class I will introduce to store partial information in a database, so this is just "cool" I suppose.
I just want to mention this one as it may be pertinent to some people working with heavy AJAX'ed sites. Particularly, if two ajax requests go through simultaneously for requests that may take a while, since sessions lock the session file, the ajax requests may be serialized but the use of this API can in some cases correct this issue when all use of the session is through.
A PHP session wrapper class
The goal of this wrapper class is to encapsulate these API to perform proper session management automatically and consistently throughout any web applications we may write. This way the intricacies of session management can be abstracted away and we can comply with best practices without much thought.
-- TO BE CONTINUED --
So we all know them, and we love them. Sessions are awesome. Why? They let us pretend our stateless protocol is stateful! Awesome, now we can write truly interactive programs.
But... in light of
You must be registered to see links
bringing a frenzy of security analysis on what we already knew was insecure -- passing session data in plaintext -- I've decided to re-evaluate my session code and to my horror I realized there are some HUGE security flaws in my code. What I deem "SSS" (same-site scripting) exploits and session hijacking capabilities galore. I decided to fix it and to write a little guide here about what's bad and what most people are doing and why it's just plain wrong. Also, the example solution I present is resistant to plain-text session hijacking, especially when a landing page is HTTP and only the log-in form is SSL encrypted.As a precursor, if you haven't heard of OWASP, but you're writing public-facing web applications, you may be a fool. You should check out their guidelines to developing securely, in particular, here's the one on sessions:
You must be registered to see links
. I'd advise you read this entire development guide as well (
You must be registered to see links
).In that document, the outlined "things to do" is one of the key issues, "If applications use both HTTP and HTTPS, they MUST regenerate the identifier or use an additional session identifier for HTTPS communications." This is key, and not doing this makes HTTPS somewhat useless as session hijacking can be used to gain sensitive information.
I want to point out before I get started that I am NOT, again NOT implementing my own session management API here, and nor should you. PHP's available session management api is sufficient to properly secure applications providing sessions. You can emulate this behavior with your own code using cookies and files or database entries but YOU DON'T NEED TO. In fact, you can add these capabilities on top of the existing session management API quite easily, so don't re-invent the wheel here.
Introduction
So let's start where the tutorials leave off. A quick google for "
You must be registered to see links
" yields the following results:-
You must be registered to see links
-
You must be registered to see links
-
You must be registered to see links
-
You must be registered to see links
The list goes on. Do you see a pattern here? Most of these pages aren't really tutorials at all, rather just an explanation of how to use a single PHP function. PHP F1 starts off decently explaining some of the information
You must be registered to see links
. But it quickly devolves into the same old thing. And we have the fun one at the end making an attempt to explain how to re-implement the session handling capabilities of PHP (yes, I do mean that HTML Goodies tutorial is BAD, do not follow its advice). The pattern is a very shallow explanation of PHP's session handling capabilities. The reason for this is because most basic PHP applications that we might make only really need a session_create and then a unset call later on clean up any session variables. That and sessions are a deceptively simple thing to deal with but are handled in a rather complex manner due to the stateless nature of HTTP and the fact that some pages may be HTTPS protected only increases this. In truth, to fully understand sessions in PHP in the most common manner (cookies), you really need to understand cookies in the HTTP protocol which, if you're so inclined, can be found
You must be registered to see links
. Cookies were added to implement statefulness to HTTP.Let's start with the basics that nobody really explains very well. HTTP is at its most basic, most essential (insofar as you care about as a developer) is just a plaintext protocol with 2 commands: GET and POST. GET requests a specific file on the webserver, to which the webserver responds to by dumping the contents of the file (and mime information, blah blah blah) back to the client, or one of various error codes indicating some error. In the case that we have a scripting language tied into this processing stack, be it ASP, PERL, PHP, or something else, this GET usually causes your script to be executed and the output of it to be sent over instead of the file itself. The POST command sends data to the server along with the request, which your script then gets through the scripting environment.
Notice we don't talk about state management. That's because state doesn't exist in HTTP. State is loosely implemented with cookies. An example cookie header sent to the client over HTTP is:
Code:
HTTP/1.1 200 OK
Content-type: text/html
[b]Set-Cookie: name=value[/b]
(content of page)
(This example was taken from the wiki page on
You must be registered to see links
, take a read for more information)Now notice the server tells the client to set a cookie with name 'name' to value 'value'. Now when the client sends a request to the server, it will send along a cookie:
Code:
GET /spec.html HTTP/1.1
Host: www.example.org
[b]Cookie: name=value[/b]
Accept: */*
This is highly over-simplified, as cookies have various other attributes such as an expiration and an option that determines whether the cookie should be sent over an HTTPS connection only or not, but we really only need to understand the basics.
So you see how you could say.. send a cookie to the client call it 'PHPSESSID' with some long unique string identifying a client, then create a file with that same name in some directory on the server, call it "C:\Sessions\" or "/var/sessions". For example, we send "PHPSESSID=30EF88B27A7AA09F" and I create files "C:\Sessions\30EF88B27A7AA09F.txt" or "/var/sessions/30EF88B27A7AA09F.txt".
Once this session is set up, every time a page request comes in, I read the cookies sent by the client, and when I find 'PHPSESSID' I look in "C:\Sessions" for that file. If it exists, I load it and pull the information out and populate some kind of an array. At the end of my page, I just need to save this array back to that file. And now you can see how I can store information about a specific client (up to the unique session ID) over the lifetime of their use of my application, called their "session."
If you're familiar with how HTTP works, you'll know the query string can also be used to send information from the client to the server. That's how GET variables work, for instance if you access a page like:
Code:
http://forum.ragezone.com/editpost.php?[b]do=updatepost&postid=6002552[/b]
Everything following the question mark, the part in bold, is the querystring of the URL entered. It's a list of ampersand separated variables (much like cookies except passed publicly and explicitly rather than privately and implicitly). You can also establish a session in this manner but it is less common and not recommended unless absolutely necessary (this is truly rare).
Sessions in PHP
Anyway, back to the topic. As you can see, the concept of establishing a session is not too difficult to understand. You also understand that at some point you want to delete their session file and if you have sensitive information there, you may want to erase it when they log-out.
This is the most basic session management you can implement. The function session_start in PHP explicitly creates a session for the client making the page request IF ONE DOES NOT ALREADY EXIST, then it fills the superglobal array $_SESSION with variables that exist in the person's session. By default it grabs the cookie 'PHPSESSID' and pulls the person's session information from a session file. You can visualize this as:
Code:
$sessid = $_COOKIE['PHPSESSID'];
$temp_session = file_get_contents(ini_get('session.save_path').$sessid);
session_decode($temp_session);
How PHP handles these cookies and session information can be modified by several php.ini settings. They are outlined
You must be registered to see links
, but the only ones I mention are below. These are by no means the most useful, so check out the rest sometime.session.use_cookies - session.use_cookies specifies whether the module will use cookies to store the session id on the client side. Defaults to 1 (enabled).
session.use_only_cookies - session.use_only_cookies specifies whether the module will only use cookies to store the session id on the client side. Enabling this setting prevents attacks involved passing session ids in URLs. This setting was added in PHP 4.3.0. Defaults to 1 (enabled) since PHP 5.3.0.
session.cookie_lifetime - session.cookie_lifetime specifies the lifetime of the cookie in seconds which is sent to the browser. The value 0 means "until the browser is closed." Defaults to 0. See also session_get_cookie_params() and session_set_cookie_params().
There is a note here in the official documentation letting you know that this time is set relative to the server's time. That is because when setting a cookie's lifetime (even using the cookie API) the timeout is offset from the server's time. If this doesn't match the client's time, weird things can happen.
session.cookie_secure - session.cookie_secure specifies whether cookies should only be sent over secure connections. Defaults to off. This setting was added in PHP 4.0.4. See also session_get_cookie_params() and session_set_cookie_params().
session.cookie_httponly - Marks the cookie as accessible only through the HTTP protocol. This means that the cookie won't be accessible by scripting languages, such as JavaScript. This setting can effectively help to reduce identity theft through XSS attacks (although it is not supported by all browsers).
session.name - session.name specifies the name of the session which is used as cookie name. It should only contain alphanumeric characters. Defaults to PHPSESSID. See also session_name().
and finally
session.save_handler - session.save_handler defines the name of the handler which is used for storing and retrieving data associated with a session. Defaults to files. Note that individual extensions may register their own save_handlers; registered handlers can be obtained on a per-installation basis by referring to phpinfo(). See also session_set_save_handler().
So notice just looking at these configurable settings for session handling that we're referred to some dynamic session API that you may not have been aware of. Notably, session_get_cookie_params and session_set_cookie_params. We'll look into using these and a few other session management API a little later on to implement sessions properly and in accordance to some OWASP best practices.
Let's go back to what people usually do. So far we've seen some basic tutorials on "how to use sessions." This will probably get you with some pages like this:
Code:
<html>
<head>
<title>My First Session Page</title>
</head>
<body>
<?php
session_start();
if( isset($_SESSION['name']) ){
$name = $_SESSION['name'];
echo('<p>Hello '.$name.', welcome back!</p>');
}else{
if( isset($_POST['name']) ){
$name = $_SESSION['name'] = $_POST['name'];
echo('<p>Hello '.$name.', I will remember you now!</p>');
}else{
echo('<form method="post" action=""><p>Please type your name: <input type="text" name="name" /><input type="submit" value="Go!" /></p></form>');
}
}
?>
</body>
</html>
Now this doesn't do a lot, but it works, right? Now you may add some log-out capability with session_destroy. But have you read what that API does from the documentation? No? Let me get that for you:
session_destroy() destroys all of the data associated with the current session. It does not unset any of the global variables associated with the session, or unset the session cookie. To use the session variables again, session_start() has to be called.
In order to kill the session altogether, like to log the user out, the session id must also be unset. If a cookie is used to propagate the session id (default behavior), then the session cookie must be deleted. setcookie() may be used for that.
WHAT? Oh well that's fine right? We delete the data so we should be fine. No. We also want to regenerate the session key. But we wan't to do this safely.
This is where "SSS" exploits and long-lived session hijacks can creep in. It also lends itself to session collision which is a terribly annoying thing to deal with.
When I talk about "SSS" I really mean session collision. But I mean it in a way that can accidentally elevate privileges in another web application. So before discussing this, let's start with what session collision is.
Let's say you have developed some web application, let's call it A. This is hosted at
You must be registered to see links
. Now in this website, you create a session like above, and you assign two session variables username and access. Let's say these two are populated when a user logs in. We know if isset($_SESSION['username']) is true, that they've logged in. And we assign the access they have to $_SESSION['access'] so we can decide what they can do with that variable.Now let's say you're working on another application on the same website. Maybe a bulletin board system for your users to get help. Call it B, and it's hosted at
You must be registered to see links
. Now when someone logs out of application A, you call session_destroy. Cool, it worked before. But wait, now you've logged them out of site B too, even if the two applications use a different authentication mechanism. Even if they use separate session variables to store the person's username and access level. This is session collision. When operations on one application's session impact another application's session. And you can easily see where "SSS" happens when site B has a separate authentication store but uses the same $_SESSION['username'] and $_SESSION['access'] variables. All I have to do is find an account on one that has elevated access on the other and create that account (if it doesn't exist) on the other. Then log-in, and navigate to the application I wanted to gain access to, and voila, I am privileged. This is essentially session-collision abused for privilege escalation.You don't really run into this until you try to run multiple applications under the same domain. This is because cookies are domain-specific. If you're using the same webserver but different virtual servers based on domain, this isn't an issue. But the other security concerns are.
The next concern is session hijacking and information leakage and/or impersonation. Applications like Firesheep do what we could already do, but make it REALLY easy. When an HTTP request goes over a wireless connection, it's in plaintext. But on an unencrypted wireless network, everyone's packets are transmitted aloud via the radio in their computer (this is how wireless works). So other people can listen in to what you're doing. Since these pages are over HTTP, they're sent plaintext, so you can easily enough pull out the cookies being sent to the server, and in this case, specifically the PHPSESSID cookie. Now you just have to modify your browser's cookies with a little bit of javascript code to modify your own PHPSESSID to theirs and you can impersonate them.
Websites that use HTTPS for authentication only are not immune. Yes, the HTTPS protects the user's password from being sent in the clear, but that's not too important. Even if the website sits in HTTPS *USUALLY* but, for some things, perhaps AJAX requests (for efficiency) uses HTTP, if the website isn't secure, you can grab the user's PHPSESSID from these requests as well and through impersonation, gain access to these HTTPS pages and see all of the same information.
The real solution is what is outlined in the OWASP (Open Web Application Security Project) guidelines. Sessions should be regenerated often, depending on how sensitive the application is. Sessions should always, ALWAYS be regenerated when a user logs out of an application. And a new HTTPS ONLY session should be generated on the first HTTPS request and, optionally, tied to the HTTP session for user verification purposes. Further, it's best to also tie in more information about a user to verify the validity of the session identifier. For instance, the IP address of the user, the browser's user agent, and potentially the client's SSL certificate. For security purposes, it's also useful to log instances where invalid sessions are seen or brute-force attempts to use invalid sessions appear (this can be done with a little session-level counting and then a call to trigger_error). In addition, my own note, is that you should also use custom named sessions to segregate session handling for each web application, so that session collision cannot happen.
So on to the session API you probably didn't know about and that we can use to implement these requirements in a nice little session class. I'm not handling caching and sessions in this tutorial but I may in a later one. It's an important feature of PHP to understand and utilize to squeak out as much performance as possible from your applications.
You must be registered to see links
- Get the session cookie parametersThis function will return an array of cookie parameters. The array returned includes the following keys:
- "lifetime" - The lifetime of the cookie in seconds.
- "path" - The path where information is stored.
- "domain" - The domain of the cookie.
- "secure" - The cookie should only be sent over secure connections.
- "httponly" - The cookie can only be accessed through the HTTP protocol.
Of these, we really only care about lifetime and secure.
You must be registered to see links
- Set the session cookie parameters.
You must be registered to see links
- Get and/or set the current session name.We can use this to implement application-specific session names to avoid collisions.
You must be registered to see links
- Update the current session id with a newly generated one.This one is just awesome. We'll use this to regenerate sessions on log-in or privilege change and periodically for high-risk applications.
You must be registered to see links
- Get and/or set the current session idWe will use this sparingly so I want to make sure you're aware of it.
You must be registered to see links
- Sets user-level session storage functionsI want to introduce this mainly to show how easy it would be to implement MySQL-based sessions, for instance. You can easily extend the Session class I will introduce to store partial information in a database, so this is just "cool" I suppose.
You must be registered to see links
- Free all session variables
You must be registered to see links
- Write session data and end sessionI just want to mention this one as it may be pertinent to some people working with heavy AJAX'ed sites. Particularly, if two ajax requests go through simultaneously for requests that may take a while, since sessions lock the session file, the ajax requests may be serialized but the use of this API can in some cases correct this issue when all use of the session is through.
A PHP session wrapper class
The goal of this wrapper class is to encapsulate these API to perform proper session management automatically and consistently throughout any web applications we may write. This way the intricacies of session management can be abstracted away and we can comply with best practices without much thought.
-- TO BE CONTINUED --
Last edited: