Protect PHP against session ID injection (session hijacking and session fixation)

Published on 01.05.2010, by Lubos Dzurik

Vulnerability of PHP session is often an issue. Apart from other security measurements, there are few ways to increase security of PHP application in an easy way. This article explains how you can protect against stealing your session ID from one computer and inject it into request from another computer.

Short note:
What I refer to as "cross site session ID injection" is commonly known as session hijacking and session fixation. I refer to both techniques under the same term because they both similarly attempt to steal session identifier from one computer and transfer it onto another with abusive intention. This article attempts to solve both security issues in a single step so there's no need to distinguish between the two terms.

Session IDs are transferred via server's super global array $_COOKIES and inserted into request headers with each request between client and server. There are many ways how to find ID of your current session - e.g. via network sniffer tools (e.g. Wireshark) or via Firebug.

Identifying session ID via cookie in request header
Fig. 1 - Identifying session ID via cookie sniffing tool (Firebug)

By reading this session ID and forcing it into request header from other computer (or attaching via GET request to URL), anybody can potentially get access to whatever you are doing on the web - e.g. to your bank account, personal details etc. Protecting against cross site session ID injection is one of the most basic preventions from hacking your site.

So - how to start session and minimize possibility of stealing your current session ID?

STEP 1 - Select dependable environmental variables

Collect selected environmental variables dependant onto requesting client browser. These variables are collected in superglobal arrays $_SERVER and $_ENV. You can get full list of available variables via throwing phpinfo() command or read here in details about PHP predefined variables.

Usually I choose following:

$_SERVER['REMOTE_ADDR']
e.g. 192.168.1.20
$_SERVER['HTTP_USER_AGENT']
e.g. Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727)

Variables above do exist in both - IIS and Apache (httpd). In order to limit validity of the session, we can also collect the date valid for actual day:

date('d.m.Y')
e.g. '31.12.2010'

From selected variables above we will calculate simple hash string which we will use for generating session_id. Session ID is a unique random string automatically generated by PHP. Once we decide what values shall be included into session ID hash, we can collect all values into array:

	$var[] = $_SERVER['REMOTE_ADDR'];
	$var[] = $_SERVER['HTTP_USER_AGENT'];
	$var[] = date('d.m.Y');

STEP 2 - Calculate hash for session name

From collected variables we can calculate simple hash:

	$hash = md5(implode('+',$var));

STEP 3 - Initiate the session

Once hash specific for current client calculated, we can set it as the name of current session:

	session_id = $hash;
	session_start();

Complete snippet:

	// collect highly dependale environmental variables
	$var = array();
	$var[] = $_SERVER['REMOTE_ADDR'];
	$var[] = $_SERVER['HTTP_USER_AGENT'];
	// limit session validity until midnight
	$var[] = date('d.m.Y');
	// calculate hash
	$hash = md5(implode('+',$var));
	// set session name from calculated hash
	session_id = 'MySessId'.substr($hash, 0, 5);
	// start session
	session_start();
    ...
TIP:
Similarly, you can use calculated hash for session_name instead of session_id.

Summary

This is a very simple way with negligible performance overhead to minimize risk of injecting your session ID by another user. If such a attacker would use your session ID from another computer, he would most likely use different version of browser or different IP access address and thus your server will automatically start new session for him and will not interact with your session.

Of course, I will not argue, that risk will be eliminated completely, it will only get reduced significantly. If the attacker is able to use browser with exactly the same signature (same version, operating system etc), inject calculated headers, or if he is able to use your computer as proxy gateway - then this approach won't be much at help. The purpose of this article was to introduce a good programming practice for any serious PHP web application.


Comments...

McSquirrel

01.11.2011 22:38
# 1 Reply to McSquirrel    
 

I would advice against using this technique to secure your site. IP address based validation is not a viable option in this age of mobile computing. The tried and true technique is using HTTPS with secure cookies that are only authorized on specific sub-domains that you fully control. If you're exposed to a man in the middle attack, it's already too late!

lubos

02.11.2011 00:28
# 2 Reply to lubos    
 

McSquirrel wrote on 01.11.2011 22:38:
I would advice against using this technique to secure your site. IP address based validation is not a viable option in this age of mobile computing. The tried and true technique is using HTTPS with secure cookies that are only authorized on specific sub-domains that you fully control. If you're exposed to a man in the middle attack, it's already too late!

I agree that this technique is not really 100% secure. It's purpose is to suggest how to increase the security rather than do nothing. In real world - how many common websites would install trusted SSL certificate? Chances that attacker will use (fake) the same IP address (and other client's properties used to calculate the sessionId hash) are extremly low, aren't they?

riptidetempora AT tormail DOT org

02.11.2012 19:57
# 3 Reply to riptidetempora AT tormail DOT org    
 

That is unbelievably poor advice. Making your session IDs deterministic is a very bad idea.

Here's a better idea: Use /dev/urandom for the session IDs (there's a php.ini setting for this), then use a session variable to store your hash (and don't use md5):

if(empty($_SESSION)) $_SESSION = hash('sha256', implode('+',$var) );
if($_SESSION != hash('sha256', implode('+',$var)) ) {
// Log the mofo out!
}

riptidetempora AT tormail DOT org

02.11.2012 19:58
# 4 Reply to riptidetempora AT tormail DOT org    
 

"In real world - how many common websites would install trusted SSL certificate?"

Well, you can get a free SSL/TLS certificate from https://www.startssl.com so there's really no excuse not to get one.

lubosdz

03.11.2012 00:50
# 5 Reply to lubosdz    
 

riptidetempora@tormail.org wrote on 02.11.2012 19:57:
That is unbelievably poor advice. Making your session IDs deterministic is a very bad idea.

Here's a better idea: Use /dev/urandom for the session IDs (there's a php.ini setting for this), then use a session variable to store your hash (and don't use md5):

if(empty($_SESSION)) $_SESSION = hash('sha256', implode('+',$var) );
if($_SESSION != hash('sha256', implode('+',$var)) ) {
// Log the mofo out!
}

Well, i will not argue about session hash entrophy - there are many ways to make secure hash (your particularly urandom would not work on windows, though).

However, original idea behind the technique above was "How to identify a user who has forbidden cookies?" (assuming apache is configured with session.use_trans_sid=0 which is default). That means if a user has session-less connection? I guess there is no way around but using some client-specific environmental variables.

Perhaps the title of this article is not quite adequate..

sandall DOT putus AT gmail DOT com

15.06.2013 03:33
# 6 Reply to sandall DOT putus AT gmail DOT com    
 

$var = array();
$var[] = $_SERVER;
$var[] = $_SERVER;
$var[] = date('d.m.Y');
$hash = md5(implode('+',$var));
session_id('MySessId'.substr($hash, 0, 5));
session_start();

Leave your comment..
Email will be converted into something like [michael AT gmail DOT com]
Note: Offensive and unrelated comments will be deleted.
Please enter result from the picture above.