Small pieces of data stored on the client-side to track and identify users.
HTTP is stateless, so they are used to implement sessions e.g. auth user, shopping carts, tracking users etc.
Like many things, the original implementation of cookies was fast and loose.
There was also no spec to begin with. In 2011, there was one RFC 6265.
Cookie is the HTTP header, while there is a key and value for that cookie.
You can think of it as a key-value store, but there are some special values.
To expire a cookie, you need to set an Expires
or Max-Age
property. You can't delete a cookie, although you can set an expiry to the past and let is expire itself.
Scoping is interesting as well. If you set Domain
as .xyz.com
, it will allow the cookie to be sent to any subdomain of that domain. This might be undesirable for domains like .vercel.com
.
It starts with the example Express app being used. It also shows the cookie-parser library being used to show us how to use it within an express app as middleware.
In the demonstration, it gives an example of a username being stored in a cookie. Bad news since they can just adjust that username.
The idea of a session is that an identifier can be associated with a session for a device. This enables us to clean out sessions quickly.
On the server, when setting the cookie, the HttpOnly
option was added to where to cookie was created. This means that JavaScript cannot be used to hijack the cookie. If you also set secure: true
, then it means the cookie can only be sent along with encrypted requests.
Signing a cookie:
To do this, on the server you can set a secret as part of the middleware, and then add it as an attribute.
In this scenario, we have a database that is maintaining the sessions of users that keeps a unique session for them.
So now instead of the user ID being sent along as a cookie, we instead pass along the session ID instead of the user ID and then behind the scenes we find out the user ID, etc.
This also enables us to clear sessions remotely ourselves.
The Origin of Same Origin Policy is essentially a tuple of the protocol, domain and port. If any of those change, they are not the same origin.
CORS (cross origin resource sharing) is the modern, accepted way to get around the same origin policy.
From Claude:
You're correct about the Origin being defined by the protocol, domain, and port. Any change to these components results in a different origin. While CORS (Cross-Origin Resource Sharing) is the modern, standardized way to handle cross-origin requests, there have indeed been other methods used historically to work around the Same-Origin Policy. Let's explore some of these techniques:
<script>
tags are not subject to the Same-Origin Policy.- The server wraps the JSON data in a callback function.- Limited to GET requests and has security implications.2. Document.domain property: - Allows relaxing the same-origin policy for subdomains. - Only works for subdomains of the same parent domain. - Both pages must set their document.domain to the same super-domain.
3. Window.postMessage(): - Enables secure communication between windows/iframes across different origins. - Provides a controlled way to send messages between different origins.
4. Reverse Proxy: - Server-side solution where your server acts as an intermediary. - Requests are sent to your server, which then forwards them to the target server.
5. Flash as a proxy: - Historically, Flash could make cross-domain requests. - Used as a bridge for making requests to other domains.
6. Server-Side Request: - Make the cross-origin request from your server instead of the client. - The server isn't bound by the Same-Origin Policy.
7. iframe and fragment identifier messaging: - Uses the fragment identifier (hash) of the URL to pass messages between windows. - Limited in the amount of data that can be passed.
8. WebSockets: - While initial handshake is subject to SOP, once established, WebSockets can communicate cross-origin.
9. CORS with Proxy: - Use a CORS proxy service that adds appropriate headers to enable cross-origin requests.
It's important to note that many of these methods (especially older ones like JSONP) have security implications and should be used cautiously, if at all. CORS is the recommended standard for handling cross-origin requests securely.
In this example, there is a store with multiple users. The SQL injection done applies the ' OR 1=1--
SQL injection script to see if anything will come back.
It also talks about the importance of having a library implements all the security features to prevent things like SQL injection as well.
If possible, use an ORM or have abstractions that are secure that you can use instead of writing things yourself (more in the case that you need to do something in the middle of the night).
There are also stored queries on databases that you could make use of. That is applicable to databases like Postgres.
This demonstration shows spread operators on JavaScript. The saving grace here is just to validate input sent across to the server.
This is even more interesting where there actually exists a property that does exist on a database but you still allow the validation of the value. Even doing something as simple as object de-structuring can save you here.
Others types that weren't covered were:
eval
.The SameSite
attribute is not the same as the original. If two addresses have the same top-level domain + one more level, they are considered the same.
There is also a public suffix list that are the exceptions to this case.
A vulnerability that allows an attacker to make unauthorized requests on the user's behalf. It attempts to trick a user's browser into doing something that they aren't trying to do.
For example, The Twitter Worm (2010) would allow you to create a post with a URL.
Netflix in (2006) allowed img tags to fetch and download movies using a URL.
The New York Times (2008) had an article that made a POST request but enabled attacked to get a user's name and email address.
ING Direct (2008) allowed an attacker to open accounts.
YouTube (2008) had issues with most requests.
TikTok (2020) allowed account take overs.
The three ingredients:
The attacker also doesn't need to access to your site.
How does it work?
In the example given, there is a login for a "bank account" with funds that can be transferred.
In the example of the "evil" address, it attempts to submit a form to make a transfer on behalf of the user.
So the way to solve this is to use SameSite=Strict
. There are still None or Lax as the other options.
This really does get back into the weirdness where a site and origin is different. Best to refer to web.dev for this one: https://web.dev/articles/same-site-same-origin
Not totally out of the woods if people have very old machines.
There is a mention of something called "the two minute rule". Not sure what it referred to... maybe two minute expiry allowed for a cookie with SameSite as None.
A random value generated by the server. If a request is either missing or has an invalid token, then the server will reject the request.
In the example, a CSRF token was rendered into the form. The token used is associated with the session on the table.
Once the form was submitted, the CSRF token was validated.
Word of warning: do not include your CSRF tokens in GET requests.
While we were here, it was also noted that you also should not include PII as part of the query params.
There are alternatives like the double-signed cookie pattern, or even add a second-step as part of the validation.
CORS is a security feature implemented by web browsers to restrict web pages from making requests to a different domain than the one that served the web page, addressing the issue of cross-origin requests.
POST requests from forms, are not covered by CORS.
Simple requests are not subject to CORS policies. It's easier to describe what is not a simple request:
application/x-www-form-urlencoded
, multipart/form-data
or text/plain
.The basic mechanics:
There is a mention in this section to not use the wildcard as it will end up causing more problems than you think.
Some additional security headers prefixed with sec-
can be used.
A type of injection attack where malicious scripts are injected into otherwise benign, trusted websites.
It comes in a few flavours:
XSS comes from within your "world" whereas CSRF comes from outside of it.
How it works:
@
broke the HTML parser.redirectTo
In this example, the demonstration was adding script tags to user input as well as to the query params.
element.textContent
instead of element.innerHTML
- the DOMPurify library has a list of these.This is a security feature that helps prevent a range of attacks, including XSS and data injection attacks. Works by allowing web devs to control the resources the browser is allowed to load for their site.
How it works:
This can be done by adding a CSP header or using a meta tag in the head of the HTML document.
In the example shown, helmet was used with the prevent certain things from running (remote loaded images, inline script tags). Other servers like Hono have their own equivalent.
For strict CSPs, disallow inline styles and scripts, eval and friends, only allowed resources loaded from highly specific, trusted resources and implement strict nonce or hash-based techniques to control script and style execution.
If you need an inline script, you can either:
A technique that focuses on clicking something else that is different from what a user perceives, effectively hijacking the page.
How it works:
There were header options here to deny this or using a frame-ancestors CSP.
This demonstration shows how using window.addEvenListener('message', ...)
can be problematic.
A big fix is just to filter out message that come from a different origin.
Tabnabbing is a type of phishing attack that exploits browser tab behavior and user trust. Here's how it works:
rel="noopener"
attribute on links:<a href="https://example.com" target="_blank" rel="noopener">Link</a>
This prevents the opened page from accessing the window.opener
object.
Referrer-Policy
header:Referrer-Policy: strict-origin-when-cross-origin
This limits the information sent in the Referer
header.
Educate users about the risks and encourage them to check the URL bar before entering credentials.
Implement robust authentication methods like two-factor authentication (2FA).
Use Content Security Policy (CSP) headers to restrict what resources can be loaded and executed on your pages.
By implementing these measures, you can significantly reduce the risk of tabnabbing attacks on your website and protect your users.
For some reason, JWTs are pronounced "jot".
The anatomy of a JWT:
Claims are name-value pairs. They carry info about a subject, like a user or device. For instance, a claim could include a user's name or role.
You can use various data as claims, tailoring security and functionality for your application.
There are different types of claims: registered claims, public claims, private claims.
JWTs are more scalable and means that you don't have to manage sessions. This can be useful for distributed systems.
JWTs can be stolen if stored insecurely. Generally, session IDs are more secure.
The vulnerability for JWTs occur when they accept weak or incorrect algorithms.
Where do you store JWTs?
With local storage, it does not support HttpOnly
cookies and is vulnerable to XSS, but persists even after the browser is closed.
With session storage, doesn't last as long but also still vulnerable to XSS.
With cookies, there are size limitations (4KB) and you need to be aware of CSRF vulnerabilities (configure SameSite
).
You should also keep JWT lifetimes short (15 minutes to an hour) and refresh them server-side.
Also ensure again that you use the secure algorithm when writing the token and decrypting.
In the final lesson, he mentioned the importance of salts for passwords because of rainbow tables (tables with cleartext passwords that were pwned).
He also mentioned the importance of getting to know things, even if they're annoying (like CORS).