Cross Site Cookie Manipulation

This article examines the security of PHP’s session cookies in a shared hosting environment, and explains why a cryptographically secure, random session ID is not enough to prevent attacks. It explains how PHP handles cookies and how the session management feature initializes in PHP. Finally, it provides an attack demo and advice for prevention.

Cross Site Cookie Manipulation

For years, we’ve been told to keep the values of sensitive session cookies unpredictable and complex in order to prevent attacks such as session enumeration. And, it made sense. If the session ID is complex, long and cryptographically secure, it's almost impossible for an attacker to guess it.

Cross Site Cookie Manipulation

However, from time to time it's a good idea to look at recommended and widely followed security practices and ask yourself: "Is this actually the most secure way to do things?" and "Is it enough?". You'd be surprised how often the answer is no. In this blog post, we discuss the security of PHP's session cookies in a shared hosting environment, and explain why a cryptographically secure, random session ID is not enough to prevent attacks.

What Changed My Mind About Cookie Security

For years, I hadn't thought much about whether random session variable values were enough to protect against session cookie attacks. Then I read a blog post by a security professional, which focuses on a Russian hacker called Alexsey Belan who hacked dozens of sites including Yahoo!. The author, Chris McNab, describes Belan's techniques in great detail. Before Belan rose to questionable fame as one of the FBI's most wanted cyber criminals, McNab and his colleague, Mike Arpaia, were tasked with investigating a security breach. After the two spent a week analyzing the servers, and forensic artefacts left behind by the attacker, they came to the conclusion that a hacker called 'M4g' was responsible for the breach. As it turns out this was one of Belan's many online aliases.

The entire Alexsey's TTPs article is a must-read (link at the bottom), but it was one of descriptions of the many clever techniques he used that grabbed my attention:

Cookies from weak non-production instances (e.g. staging) were valid in production as cryptographic materials were the same — bypassing 2FA.

I suspect this means that he could authenticate to a less secure development instance of the live website as an administrative user, copy the cookie that was generated during authentication and use it to authenticate against the live production website. I can't decide whether this is an extremely clever trick or just bad software design! It's probably a little bit of both.

It got me thinking. The cookies were most likely cryptographically secure from the outside. The problem was that once he hacked a lower level target, namely the staging server, he could easily hack the production application, as they seemed to have shared the same secret. I was inspired to take this further and thought about an attack on PHP (and possible other languages or frameworks that stored session information in a similar way) if it ran on a shared host.

So why was I prompted to think about PHP and shared hosting environments? At this point, it would be useful to remind ourselves of how PHP handles cookies.

Cookies in Session Management

You may know that HTTP is a stateless protocol. It needs a mechanism like HTTP cookies in order to manage and differentiate users. Using cookies, servers can identify the users that make requests and grant them the necessary access.

As the name suggests, users have control over client-side technologies like browsers that store and send cookies. Therefore, instead of leaving sensitive data on browsers, where it can be read and modified, most developers – and PHP – leave a unique, identifiable key on the browser, and establish communication with the server using that key.

This is similar to what might happen when you visit a government agency. You show your ID to an employee, proving that you are the person you say you are. The employee knows he can safely talk to you about that unpaid parking ticket from three weeks ago, without giving out any sensitive information to an unintended recipient. But, while most people wouldn't have a problem with a stranger paying their parking tickets, the situation is clearly different when it comes to your personal email, online banking or social media accounts. This is why we have cookies, and why they do exactly what they are supposed to do, if used correctly.

In a similar way, whenever a user makes a request, a session ID is sent along with it. PHP finds the session related to this cookie and initializes a session object containing sensitive data, such as your email address or account balance, which allows the website to manage the user's session.

If you want to find out about PHP's session management, take a look at php.ini, PHP's main configuration file. By default, the name of the cookie that holds the session ID is called PHPSESSID, as illustrated.

You can also see where the sessions are saved by looking at the session.save_path configuration option.

How the Session Management Feature Initializes in PHP

PHP won't start a new session by default. Instead, the developer needs to call session_start in order for PHP to read the PHPSESSID cookie content and initialize the session with its value. However, there is also a php.ini configuration option called session.auto_start, which will enable cookie initialization automatically. But this option is not activated by default. To enable this session management feature, you have to call the session_start function. When using the default configuration, as seen above, this function call has the following effect.

  1. PHP tries to find a session file in the /var/lib/php5 directory. The name that the filename contains is the value of the PHPSESSID cookie. If it finds the file, PHP initializes an array and puts it into the super global $_SESSION variable.
  2. If the file does not exist, it creates a new one. The filename consists of the prefix sess_ and the value in the PHPSESSID cookie.

This is how information is stored in session files:

<?php
session_start();
if(authenticate($user, $pass)) {
$_SESSION["loggedIn"] = "yes";
$_SESSION["last_login"] = date("Y-m-d H:i:s");
} else {
echo "Try again!";
}

For this code, the information highlighted below was written to the file. Here is the output of the session file.

For a more readable output, you can use this code:

<?php
session_start();
var_dump($_SESSION);

This is the output of the code above:

array(2) { ["loggedIn"]=> string(3) "yes" ["last_login"]=> string(19) "2016-02-19 13:48:20" }

The Attack on Cookies in Shared Hosting Websites

Let's see how we can attack this issue in a shared hosting environment. In this example, there are two sites: fvvitter.com and qoogie.com. The first website, fvvitter.com, is under my control; qoogie.com belongs to someone else. But, both websites are on the same server because of shared hosting.

I began to investigate which session files are saved to which directory and what permissions are needed to access them.

<?php
system("ls -ld /var/lib/php5");

I immediately gained access to the names of the session files in the directory. You may remember that the filenames contained the actual session ID. That means I could create a cookie called PHPSESSID, put the ID from one of the filenames into the cookie, and visit qoogie.com. This was enough for me to take over the account of the person that owned this ID.

However, mere luck may not always be sufficient. On other shared hosting websites, you may run into strict directory permissions which don't even let you list the items within the directory. That doesn’t necessarily mean you can’t proceed. Even though you cannot access the list of items in the directory, you can still take over an account. This is how I was able to do that.

Both qoogie.com and fvvitter.com run on the same system. Let's say an online shop like Opencart is installed on each domain. That means they use same session structure, or in other words, they would save the same values with the same name inside the session array in order to check if a user is authenticated.

If I can manage to login into fvvitter.com's Opencart instance as an administrator, I can use the same cookie on qoogie.com.

The strict permissions were in my way again, as qoogie.com couldn't access the session file that my Opencart instance generated. That didn't matter though, since I owned the file, and I could give everyone read and write permissions.

Here’s the code to make the appropriate changes, if the session ID was a92q1u6m6grgeco1glv8eip8i3:

<?php
system("chmod 777 /var/lib/php5/sess_a92q1u6m6grgeco1glv8eip8i3");
// check permissions
system("ls -ld  /var/lib/php5/sess_a92q1u6m6grgeco1glv8eip8i3");

This did the trick. When I tried to load qoogie.com using fvvitter.com's cookie, I was able to manipulate session handling of the site and bypass the necessary authentication.

How to Prevent Cross Site Cookie Manipulation

If you have multiple websites on your server, you need to prevent an attacker from reading the names of your session files. You can do this by setting the appropriate permissions. However, an attacker may still be able to use cookies generated by another website on the same server in order to take over an account. Therefore, it's important to store the session files in separate locations. You can use the session.save_path configuration option to select an appropriate folder.

Further Reading

Alexsey's TTP's
Cross Domain Cookie Manipulation 1 (Video)
Cross Domain Cookie Manipulation 2 (Video)