Introduction
In 2019, I published an article on Medium about Cookies and CORS (Dealing with Cookies and CORS). It received positive feedback and was even copied by others. Please remember the original author. The article discussed what CORS is and how to correctly implement it on the frontend and backend, which is a common issue in web development.
Over a year later, Chrome released the Cookie Policy Change (defaulting SameSite to lax). This made me reconsider the topic and I wrote my thoughts in the article Reflections on Chrome Cookie Policy Adjustment, discussing my questions about SameSite
. There are still two questions that I am seeking answers for:
- So, when the server adds
SameSite
, is it no longer necessary to implement CSRF protection? If users use outdated browsers (which do not support the SameSite attribute) and this causes security issues, is it the responsibility of the company or the browser? - Is using
Cookie
really an infallible method?
Regarding the first question, I believe that as time progresses, more and more browsers will catch up with the times, and CSRF protection mechanisms can be completely phased out.
The second question arises from several cases I have come across, so let me elaborate:
Before we proceed, let's do a little test. What is the scope of setting the domain as .example.com and domain as example.com in a cookie?
We'll look at the answer later, but first, let's examine a classic case - the Cookie Bomb.
1. What is a Cookie Bomb? 💣
The article demonstrates the Cookie Bomb using Github Pages.
Apart from hosting code, Github can also be used to create Github Pages as personal websites or blogs, and Github provides a domain name in the format user.github.io
(formerly username.github.com
, see this article for reference).
In most browsers, a cookie (including key, value, expires, etc.) can hold up to 4KB of data, with a maximum limit of 50 cookies (may vary slightly depending on the browser). This means a total of 4KB * 50 = 200KB. The Cookie Bomb technique involves writing a large number of cookies on the client-side using document.cookie
. Since cookies are included in all requests by default, 200KB is a significant amount for a GET request. Therefore, in general, the server or the nginx layer will block and ignore such requests.
Now, here's the biggest issue: Cookies are automatically applied to all subdomains (with some exceptions). So, when you are a victim on webpage A (after being flooded with cookies), the effects will also be seen on webpages B and C. This makes all Github Pages appear broken.
Strictly speaking, the Cookie Bomb does not pose a security issue; it's just a bit of a prank. Victims can simply clear the excessive cookies to ensure correct handling of requests by the server. However, it does seem strange that cookies are shared among blog services like this.
To avoid this problem, some subdomains within a domain do not share cookies with each other. This is known as the public suffix list, maintained by Mozilla, which contains a list of all public suffix domains.
Returning to the earlier question, domain=.example.com
and domain=example.com
are the same; they apply to all subdomains.
If the domain is not set, it will at least be limited to the same domain by default. When it is set, the cookie is sent to all subdomains.
2. Do you really understand how Cookies work?
Modern browsers should implement Cookies according to the RFC6265 standard. For example, how should Cookies handle duplicate values? Do Cookies need to be sorted?
Based on my current experience, it is rare to find an engineer who truly understands the various intricacies of Cookies and knows the specification details. This is normal, in my opinion. Most engineers know how to set Cookies, understand the operation of httpOnly
and secure
, and grasp the logic of path and domain. That's usually sufficient. After all, to build a product, it's not necessary to read all the specifications and fully understand the ins and outs of Cookies.
Even if all the above aspects are successfully protected, it is still necessary to consider how your chosen framework parses and implements Cookies to achieve 100% security.
Rethinking: Another Possibility
Based on the various cases mentioned earlier, I started thinking about the best practices, which led me to focus on JavaScript
. Let's see how JavaScript
addresses these issues.
In my previous article, I also mentioned a reference article: Cookies are bad for you. Let's review it here.
Requests sent by JavaScript, such as AJAX or fetch, are defined as CORS requests. CORS requests come with certain conditions and restrictions. For detailed information, you can refer to my previous article or the MDN documentation. Here, I will describe how CORS protects your requests:
- Cookies are not automatically sent/received in cross-origin requests: This prevents CSRF attacks because CORS requests come from JavaScript code, making typical CSRF attacks ineffective.
- CORS requires JavaScript execution: We can place the authentication mechanism in the
Authorization
request header. Regular form submissions and hyperlink attacks cannot include request headers, thus effectively preventing CSRF. - Headers can be added to the request: Fetch allows dynamic addition of headers for authentication purposes. For example:
fetch('/api', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
})
- If authentication does not rely on Cookies: No need to deal with the complexities of Cookies or worry about CSRF implementation issues.
Now, let's address the most pressing questions:
- If
token
is stored inlocalStorage
, isn't it vulnerable to XSS attacks? - What if the user has JavaScript disabled?
Regarding question 1, I am still contemplating this issue, and no one seems to have answered: "So, if we use Cookies and they are subject to XSS attacks, wouldn't that be a problem too?"
The difference lies in whether the attacker can obtain the token. Let's consider what happens after an XSS attack:
- Cookies: XSS occurs → Attacker injects malicious code → Extracts data
- localStorage: XSS occurs → Attacker retrieves token → Extracts data
I see that the current discussions only focus on the second step, but no one seems to discuss the fundamental difference between the two. It's a missed opportunity, in my opinion, because everyone thinks Cookies are great and secure, while localStorage is heavily criticized. But is that really the case? Are Cookies truly infallible? Does the complexity of the specification lead to more potential issues? I haven't seen many people discussing this aspect.
Regarding question 2, if we choose to rely on the localStorage mechanism, there will naturally be some trade-offs. In fact, most users have JavaScript enabled to browse websites. If JavaScript is disabled, I believe most SPA websites that do not implement SSR will simply not work.
Afterword
Although I have some doubts about Cookies and whether the implementation in frameworks is robust enough, I believe that the evolution of the Cookie specification has become increasingly stable and secure. If a framework is open-source, it should be trusted. The implementation of SameSite
can be applied to most browsers, so it seems to be a promising future. However, I still hope for deeper discussions and debates, which is why I wrote this article.
The above represents my understanding of Cookies. If there are any mistakes, please feel free to point them out.
Supplement
After publishing the article in the community, there were some suggestions in the comments section that you can check out.