Back in the 90s, someone wanted to store information on the clients, only it was not possible yet. Luckily the Netscape team quickly came to the rescue and implemented cookies. This was a very significant move which shaped the things to come. Cookies became the de-facto state in the statelessness of HTTP. Today they are essential and their security is critical. Here is what you need to know.
Before getting into how cookies work let's take a quick look at the basics. Cookies are essentially key-value pairs attached to HTTP requests and responses in the form of headers. The server sends cookies to the client using the Set-Cookie header. In return, the browser sends these back with every subsequent request using the Cookie header.
It is important to note that cookies are managed by the browser and not the web application running in the browser. Indeed, the client app has limited control over cookies. This is a great thing, as you do not have to overtake the very complex task of managing it yourself. In the meantime, you can influence a whole lot of details which can make or break your application's security.
Interested? Read on!
The design of cookies is evolutionary and not necessarily in a good way. Security was bolted on cookies, not built in. This “patching” is not yet finished; we still keep adding security features to them as we discover new threats. The cookies spec has 3 RFCs (2109, 2965, 6265), and the last one gave up on specifying behavior. It only describes the current state!
Let's see what it is. Head over to GitHub. Log in and open your browser's developers tools and find the section about cookies. Here is what it looks like in Google Chrome.
So what does all this mean? I'll skip over the Name, Value part as that is pretty self-explanatory. On to scoping then.
Surprisingly, cookies do not adhere to the same origin policy. They have a relaxed version of it that is controlled by the Domain, Path, and Flags parameters.
This parameter tells the browser where to send the given cookie. The domains in our sample include github.com and .github.com. Note the dot in front of the second example. That is no typo. The former means, send this cookie whenever you request something from exactly github.com. The latter is more general and instructs the browser to attach it to any request where the domain ends with github.com.
One may wonder, what domains can I set cookies for? At first, you may think that you can only set cookies for the origin you are using to serve requests. That is not so. It is possible to set a cookie on subdomain.example.com which will be sent to subdomain2.example.com as well. In fact, if you are serving your page from a.b.c.d.com you can set cookies for parts of the domain like so: .b.c.d.com, .c.d.com, .d.com.
Can you set one for .com? Fortunately, no! Browsers maintain a public suffix list and reject cookies with domain attributes matching them. Guess why?
The original specification also outlined a Path parameter, which was designed to restrict cookies to specific paths. This mechanism has proved to be utterly useless for separating applications on the same domain securely. Today, in the “age of virtual hosting", this feature is rarely used. Feel free to leave it on the default “/".
This parameter tells the browser when to remove the cookie. The value is either a date or the word Session. The browser will eject the cookie from its jar when the specified time arrives. Or in the case of session cookies, when the browser is closed.
There is another crucial feature at play here. Ideally, session cookies are never written to disk. Some browsers break this rule to trade security for usability.
Here is a brief summary of what the flags mean.
|Secure||When set, instructs the browser to only send this cookie over a secure (TLS) connection. Aimed to protect against a network attacker.|
|SameSite||When set to "Lax" or "Strict", instructs the browser not to send the cookie if the request is originating from a 3rd party site. Aimed to protect against Cross Site Request Forgery attacks. As of this writing, this flag is really fresh. Here is the draft.|
One drawback of cookies is that only the Name and Value part is sent back with subsequent requests. Therefore, a server cannot distinguish a Secure cookie from a non-secure one or check the domain parameter for an exact match. These problems are solved by cookie prefixes. They allow the server the use special prefixes (__Secure-, __Host-) in the Name of the cookie to enforce stricter security. This feature is the newest addition to the list of bolted on security measures.
Take a second to think about the integrity of cookies. The server blindly trusts the client to send back the cookies unmodified! The client can modify the cookies as it pleases. Event worse, other parties on the network may be able to do the same. Long story short, if your application requires cookie integrity, you will have to provide it yourself. I will cover integrity protection in another post.
Cookies are the state of the HTTP protocol. They can make or break application security. They are the rare exception to the same origin policy and have quite a few bolted on security measures like flags and prefixes, yet they are still missing some like integrity protection.
Like always, your cookie settings will need to match your use case. There is no one size fits all solution. After reading this you are armed with the knowledge; now it is up to you to use it!