Cookies are metadata used to keep track of HTTP sessions. This is because HTTP is a stateless protocol — each HTTP session is independent of another.
A website sets a cookie in your browser to recognize you. This allows you to continue your browsing activity from where you left off (online shopping for example) because your browser includes that cookie every time you make subsequent visits to the website in the future.
According to RFC 6265, cookies are included in either the Cookie
or Set-Cookie
header fields. Since a website sets cookies into the browser of the client, the Set-Cookie
header is seen in an HTTP Response from the server. When the client returns to the website in the future, it will include these cookies in the Cookie
header within the HTTP Request. The Cookie
header sent by the client contains cookies that were set previously by the webserver in the Set-Cookie
header.
We can observe the setting of cookie headers by sending an HTTP Request to a website we not visited recently. Let’s go to www.cnn.com and intercept it with Burp Suite.
There are no Cookie
headers sent by my browser because this is not a website I have visited at all. It’s the first time I am visiting this webserver.
Zoom image will be displayed
The webserver at responds. In the HTTP Response, we can see that the HTTP Server at www.cnn.com sets a few cookies in the Set-Cookie
headers as seen below:
The server has set 4 cookies: SecGpc
, countryCode
, stateCode
, geoData
Set-Cookie: SecGpc=0; Domain=.cnn.com; Path=/; SameSite=None; Secure
Set-Cookie: countryCode=SG; Domain=.cnn.com; Path=/; SameSite=None; Secure
Set-Cookie: stateCode=04; Domain=.cnn.com; Path=/; SameSite=None; Secure
Set-Cookie: geoData=singapore|04|423466|SG|AS|800|cable|1.320|103.920|-1; Domain=.cnn.com; Path=/; SameSite=None; Secure
These cookies in particular are designed to store the client’s geolocation information. This information is useful for cnn.com to customize content based on the region I am visiting from, among other reasons such as regional based compliance (that’s one rabbit hole I will pursue on another day).
All four of these cookies have the attribute: Domain=.cnn.com
. These cookies are scoped to .cnn.com
which means that my browser will include these cookies in HTTP requests made to all cnn.com
subdomains such as edition.cnn.com
, www.cnn.com
, etc.
In this case, we can see the HTTP Response is redirecting us with a 302
Response Code to another subdomain which is specified in the location
header: edition.cnn.com
HTTP/2 302 Found
..
..
..
Location: https://edition.cnn.com/
Although we are being redirected to another location at https://edition.cnn.com/ we should still expect to see these cookies in subsequent HTTP Requests made from my user-agent to the server because of what I mentioned previously about the scope, so let’s have a look at what the next request looks like.
In the very next GET
request to edition.cnn.com
, we can see a Cookie
header which includes all four cookies (SecGpc
, countryCode
, stateCode
, geoData
) previously sent in the Set-Cookie
header by the web server.
Zoom image will be displayed
GET / HTTP/1.1
Host: edition.cnn.com
Cookie: SecGpc=0; countryCode=SG; stateCode=04; geoData=singapore|04|423466|SG|AS|800|cable|1.320|103.920|-1
..
..
..
According to RFC 6265 these cookies will expire at the end of the session since there were no Max-Age
or Expires
attributes set in the Set-Cookie
header by the website. These are also known as session cookies. They expire when the browser closes.
RFC 6256: Unless the cookie’s attributes indicate otherwise, the cookie is returned only to the origin server (and not, for example, to any subdomains), and it expires at the end of the current session (as defined by the user agent). User agents ignore unrecognized cookie attributes (but not the entire cookie).
RFC 6265 section 4.1.2: When the user agent receives a Set-Cookie header, the user agent stores the cookie together with its attributes.
Cookie attributes are additional pieces of information that control the behavior of cookies. They can determine when a cookie expires, or if there should be a security measure associated with them.
The following attributes dictate the lifespan of a cookie:
Expires
: Represents maximum lifetime of a cookie as the expiration date and time.Max-Age
: Represents maximum lifetime of a cookie in number of seconds.In both cases, the user-agent is allowed to evict the cookies before the expiry. If a cookie contains both these attributes, the Max-Age
attribute has precedence and controls the expiration date of the cookie.
Other attributes include (but are not limited to) the following:
Domain
: Specifies hosts to which the user-agent will send the cookie. If google.com sets a Domain
attribute to a cookie, then the client’s user-agent will send that cookie in HTTP Requests to google.com, www.google.com, and www.example.google.com.Path
: The scope of a cookie is limited to a specific path/URI such as /results
Secure
: The user-agent will only include this cookie in an HTTP Request over HTTPS.HttpOnly
: Limits the scope of the cookie to HTTP only. This prevents scripts (such as Javascript) from accessing the cookie.There are several cookies that are intended to mitigate web application security risks. According to OWASP, the HttpOnly
cookie helps mitigate the risk of client side script accessing the protected cookie. Browsers that support the HttpOnly
flag will prevent revealing the cookie to a third party.
Cross site scripting (XSS) attacks target cookies that may contain session identifiers. An attacker who can access the cookie, can steal it and replay it to impersonate the victim. This is also known as Session Hijacking.
How would a browser prevent a third party from accessing a cookie with the HttpOnly
flag enabled? I want to test this by configuring my Wordpress website to set a test cookie with the HttpOnly
flag. I have added the following code into the functions.php
file of my current Wordpress Theme. It will instruct my Wordpress site to set a test cookie.
test_cookie
secret_value
add_action('init', function() {
setcookie('test_cookie', 'secret_value', [
'expires' => time() + 3600,
'path' => '/',
'secure' => is_ssl(),
'httponly' => true, # We are testing the HttpOnly flag
'samesite' => 'Lax'
]);
});
Now when I visit my site, the server sets the test cookie in the HTTP Response. We can view this in Google Chrome’s developer tools under Network -> Cookies. HttpOnly
is checked.
Zoom image will be displayed
Next, I try running this command in the console: document.cookie
and I am unable to see any information. There is no output. That is because my browser doesn’t allow me to read the test cookie and its value that was stored earlier because of its HttpOnly
attribute.
The
document.cookie
property in web browsers allows client-side JavaScript to read and write cookies associated with the current document. However, cookies set with theHttpOnly
flag are a specific exception to this rule.
What if I create a new test cookie named: new_test_cookie
without the HttpOnly
flag by modifying the earlier code? I will toggle HttpOnly
to ‘false’
this time:
new_test_cookie
secret_value
add_action('init', function() {
setcookie('new_test_cookie', 'secret_value', [
'expires' => time() + 3600,
'path' => '/',
'secure' => is_ssl(),
'httponly' => false, # Toggling httponly to false
'samesite' => 'Lax'
]);
});
If we reload the page again, we see 2 cookies now. One is set without the HttpOnly
flag:
Zoom image will be displayed
This time, when I run document.cookie
into my console, I am able to see the newly created cookie’s name and value. This is because this cookie was not set with the HttpOnly
flag.
The original test_cookie
with the HttpOnly
flag enabled is still not revealed. Similarly, other attempts to read or modify HttpOnly
cookies using Javascript will fail. This is a simple way to explain how a browser can protect a victim from having their session ID (SID) stolen by an attacker, because of the webserver opting to attribute the HttpOnly
flag to a sensitive cookie.
Should all cookies include the HttpOnly
attribute? In order to prevent sensitive data leakage, cookies such as session identifiers and authentication tokens should have this flag enabled.
On the contrary, some cookies when set are not attributed with the HttpOnly
flag. One example is bot defense cookies inserted by reverse proxies such as F5. These reverse proxies will use JavaScript challenges to determine if the connecting client is a human or bot.
The JavaScript challenge runs in the browser and collects telemetry such as mouse movements. It then sets a cookie using JavaScript, typically with document.cookie
, to store the telemetry token for validation on future HTTP requests.
Since JS needs to read/write the cookie during telemetry collection, it cannot be set with the HttpOnly
attribute.