PhpRiot
News Archive
PhpRiot Newsletter
Your Email Address:

More information

Bake Cookies Like a Chef

Note: This article was originally published at Planet PHP on 12 December 2011.
Planet PHP

It's Christmas time again. While you write the first lines of your shiny new authentication library, the smell of tasty cookies is slowly sneaking into your room. The few minutes until they are finished is a good time to lean back and think about cookiesa-anot the ones in the kitchen, but the cookies in your browser.

Let's explore how to securely store data on the client side, and how to detect unwanted modifications.

Ingredients

Nearly every web app needs some kind of authentication, and most use cookies to help with identity. Cookies are a very convenient, portable, and scalable method to identify users after they have been authenticated. Unfortunately, you have to store those cookies on the client. The phrase aoall user input is evila is often used when you have to deal with data from the client, so it is important to realize that cookies are under the client's control.

The first thing to remember is that cookies can provide more information to the user that you might think. For example, if your cookie contains a PHPSESSID key, an attacker doesn't need fancy attack tools to immediately recognize that your app probably uses PHP. Every bit of information is important for an attacker, even if it doesn't seem important.

An example from the Microsoft world is if you see ISAWPLB in a cookie, chances are that the target app uses the ISA Server as a reverse proxy or load balancer. Just look at the cookies of apps that you use regularly, and Google for some of the keys. Attackers can use this information in ways you would never consider. If you don't believe me, take a look at the presentation entitled How I Met Your Girlfriend, by Samy Kamkar, which was presented at DEFCON 18. He shows you how to reduce the entropy of PHP sessions from 160 bits to 20 bits, which is much easier to brute force.

Attackers may also use cookies to bypass your authentication mechanisms. For example, if you implement a aoremember mea feature by storing the user ID in the cookie (and no additional checks are performed on the server side), then the attacker may randomly change the ID in the cookie until he finds a valid one. It's not very hard to guess that admin users have low IDs, right? If you want to learn more about attack vectors like this, I recommend you to start with the OWASP Top Ten Project where you can read about the top ten security risks in web apps, and how you can prevent them.

Does all of this mean that you should avoid cookies altogether? Of course not. Let's take a look at two ways to encrypt your cookies and detect possible changes made to them. Note that the following code snippets are taken from the Lithium core. (Make sure to check it out if you haven't already!) Additionally, I've modified the code snippets a bit to make them easier to understand. Lithium handles the following techniques transparently, so in practice, you only add and read data and don't need to care about the implementation details.

Directions

A secure way to ensure that no one has tampered with your cookies is called HMAC (Hash-based Message Authentication Code). In a nutshell, your cookie gets signed with a hash that is generated from your data and a secret password. As a result, if the data changes, the hash will change, too.

PHP provides a handy hash_hmac() function that does the actual work for you. Here's the code snippet from Lithium:

public static function signature($data, $secret = null) { unset($data['__signature']); $secret = ($secret) ?: static::$_secret; return hash_hmac('sha1', serialize($data), $secret); }

It all boils down to calling hash_hmac() with an algorithm (in this case sha1), the actual data, and a secret password. Because you can only hash a string, you may need to serialize a non-scalar payload first. The signature will be appended to the payload, so you need to unset it first, so that the hash actually represents just the payload. It looks similar to this (let's pretend that the static signature method is wrapped in a Cookie class and is public):

// password and payload $secret = 'phpadvent'; $data = array('christmas' = 'fun'); // create the signature $signature = Cookie::signature($data, $secret); // store the cookie $data += array('__signature' = $signature); setcookie('mycookie', serialize($data));

To verify the cookie, remove the payload and generate the hash again. If the two hashes don't

Truncated by Planet PHP, read more at the original (another 6796 bytes)