Restricting access to cookies is essential for security in many web apps. For example, the session ID, the secret token used to identify a particular session, is typically stored in a cookie. Cookies have several important settings. Previously, I discussed the secure flag. This time, let’s dive into the cookie domain.
The cookie domain is an important security feature, probably even more important than the secure flag. It tells the browser that this cookie must only be sent to matching domains. Matching however, can happen in several ways. Perhaps domain is a bit of a misnomer: this can be any host name, like
With this in mind, I did some digging into the exact workings of cookie domains, and was surprised to find this less straight forward than I had expected. And, it turns out Internet Explorer’s RFC-incompliant behaviour makes it safer to host your websites with a www-prefix, so
www.erik.io instead of
Update: this post was updated on April 9, 2014, to reflect that Internet Explorer misbehaves with domain-less cookies, as learned from this blog post. Previously, I concluded that a www-prefix makes no difference, with this new knowledge, a www-prefix is safer.
- When not setting an explicit domain for a cookie, the default in most browsers is to only send the cookie when the domain matches exactly. However, Internet Explorer violates the RFC, and will send it to all subdomains as well.
- The most compatible way of having a cookie visible to this domain and all sub domains, is to prefix it with a dot, like
example.comand all sub domains (and their sub domains, etc.)
- If you set a cookie domain without the dot prefix, like
erik.io, this will still be treated as “
erik.ioand all sub domains”. Additionally, this is invalid in very old implementations.
- Setting an explicit cookie domain may therefore actually decrease security, as you will now include all sub domains in all browsers. For Internet Explorer, there is no difference.
- To keep your cookies safe, host your websites with a www-prefix, so
erik.io, as in the latter case, Internet Explorer will also send the cookies to any (perhaps malicious) subdomain of
erik.io(in violation of the RFC).
- Regardless of your cookie domains, if you place or modify sensitive data in sessions, make sure to rotate the session ID to prevent fixation attacks.
- None of this excuses you of the responsibility to keep your domain clean from untrusted hosts. If you want to host untrusted user content, place it under a different domain.
I have tested this behaviour with the latest Firefox, Safari and Chrome under Mac OS X.
Domain matching specification
Cookies were defined in three RFCs, from 1997, 2000 and 2011, each succeeding the other.
RFC 2109 and RFC 2965
RFC 2965, published 2000, defines that exact matches are always considered a match, unsurprisingly. For partial matches, it states (where A is the request host name, and B is the cookie domain):
A is a HDN string and has the form NB, where N is a non-empty name string, B has the form .B’, and B’ is a HDN string. (So, x.y.com domain-matches .Y.com but not Y.com.)
In other words, if the cookie domain is
erik.io, and the browser performs a request to
www.erik.io, the cookie is not a match and will not be sent. If the cookie domain is
.erik.io however, it will match.
However, it also specifies:
If an explicitly specified value does not start with a dot, the user agent supplies a leading dot.
So if you would set the cookie domain to
erik.io in the header, the user agent should treat the cookie domain as
.erik.io, and it will match with
Now, it may be that RFC 2965 was referring only to the Set-Cookie2 header. However, the older RFC 2109, from 1997, differs in only one part:
An explicitly specified domain must always start with a dot.
So in RFC 2109, setting a cookie domain to
erik.io is simply invalid – you must set it to
.erik.io for it to be valid, which means it will also match
However, this leaves an interesting question. What happens if no cookie domain was specified at all? Both state:
Domain: Defaults to the effective request-host. (Note that because there is no dot at the beginning of effective request-host, the default Domain can only domain-match itself.)
Therefore, in RFC 2109/2965 implementations, if a request is done to
erik.io, and a cookie is set without an explicit domain, the domain defaults to
erik.io – not to
.erik.io! In that case, it will not match
foo.erik.io. So a cookie set by a request to
erik.io without an explicit domain, is very different compared to explicitly setting the
erik.io domain in the cookie, as the latter is treated as
The successor of RFC 2965, RFC 6265, was published in 2011. This means we will still encounter browsers that have not adopted to this specification. It has a somewhat different approach in section 5.1.3:
The domain string is a suffix of the string and the last character of the string that is not included in the domain string is a %x2E (“.”) character.
In other words, the
erik.io cookie domain matches the
foobar.erik.io request. So what about using
Note that a leading %x2E (“.”), if present, is ignored even though that character is not permitted
.erik.io is identical to
erik.io. However, there is another special condition:
If the server omits the Domain attribute, the user agent will return the cookie only to the origin server.
So if a request is made to
erik.io, a cookie is set without an explicit domain, the user agent must match it only to
erik.io, and not to
foobar.erik.io. The RFC notes that not all user agents might handle this correctly.
And then there is Internet Explorer
Unfortunately, this is only what the RFC specifies. Internet Explorer intentionally deviates from this (see Q3), although the reason for this is unknown to me.
In Internet Explorer, a cookie without an explicit domain is also sent to all subdomains. That deviation means that a cookie set without a domain from
erik.io is more at risk than
www.erik.io. In the former case, Internet Explorer will still send it to any subdomains of
erik.io, in the latter case only to subdomains of
www.erik.io, which will be less common. In the non-www case, just a single malicious host under that domain compromises all cookies you’ve set.
I built a simple test case for this scenario and users of Internet Explorer 9 and 11 reported that their browsers misbehaved.
Although the definitions are somewhat different, we can simplify it for any of these implementations as:
- When no domain is set in the cookie, the cookie should only match the exact host name of the request. No sub domains, no partial matches. This means simply not including the domain attribute – it is not valid to set an empty domain attribute. Unfortunately, Internet Explorer appears to treat this as the host name along with any subdomains.
- When setting a domain in the cookie, the safe choice is to have it preceded by a dot, like
.erik.io. The cookie will match with all sub domains.
- Setting a cookie domain without a preceding dot, like
erik.io, is invalid in RFC 2109 implementations, and will produce the same behaviour as with a preceding dot on other implementations. There is no way to restrict a cookie to a specific explicitly set domain, without sub domains being included.
Other worthwhile observations:
- In all RFCs, a specified cookie domain must match the current host name, per normal matching. Setting a cookie for
www.erik.ioin a response from
erik.iois not valid, as a cookie with domain
www.erik.iodoes not match
erik.io, the former being more specific.
- In RFC 6265, domains are explicitly lower cased when parsing the Set-Cookie header.
Malicious hosts under your domain
Although cookie domains do help to limit the scope of your cookies, it is still best to avoid having untrusted hosts under your domain. This is why GitHub pages are hosted under github.io, not github.com, for example. The risk is that any page under the same domain, can always place cookies at the domain-level.
One way to abuse this, which can not be fixed with any domain setting, is session fixation. Let’s say I control
bad.example.com, and a Django site on
example.com uses session cookies. I trick the victim into visiting
bad.example.com, and set a
sessionid cookie for
.example.com. Remember, you can always set a cookie for a less specific domain, so this is allowed. I use a valid session ID, which I just got from the real
example.com. Then, the victim visits
example.com and the browser sends my valid
sessionid cookie, because
.example.com matches requests for
example.com. The user logs in, and now their session, which I can access because I know the session ID, gives me access to their account.
Fortunately, this attack does not actually work in Django, because Django replaces the session ID when something significant happens, like a login or logout. At that point, the session ID I had is no longer useful. Remember to rotate the session ID as well for your own apps, when you add sensitive data to a session.
Shameless plug: an additional method of securing your sessions is my package django-restricted-sessions. In that case, the session would already have been reset as soon as the victim visits
example.com and sends the planted
sessionid cookie, unless the IP and user agent matches as well.
What about third party cookies?
Third party cookies are cookies placed for different domains than the page you are viewing. For example, if you visit
erik.io, and that results in a cookie being set for
example.com, we consider the latter a third party cookie.
Third party cookies are not prevented by domain matching, because the domain matching happens at the request level. A single page may result in many requests to many different domains. For each of those requests, the browser runs its cookie matching code to see which cookies to include. In the case I described, it might be that
erik.io includes an image from
example.com. When loading the image, the browser may receive or send cookies belonging to
example.com, because that’s the host name for that particular request. If other pages also include an image hosted on
example.com, the browser will send the cookies it received from previous requests. This is how third party cookies can be used for tracking across different websites.
Many browsers have an option to disable third party cookies. I haven’t looked into it, but logical way to do this, would be to add the restriction that cookies must match the domain of the URL of the page the user is viewing.
From this blog, you may also like: