What is CORS and Same-Site
1. Same-Origin Policy
The same-origin policy is a critical security mechanism that restricts how a document or script loaded by one origin can interact with a resource from another origin.
Web content’s origin is defined by the scheme (protocol), hostname (domain), and port of the URL used to access it. Two objects have the same origin only when the scheme, hostname, and port all match.
|
It helps isolate potentially malicious documents, reducing possible attack vectors.
For example, it prevents a malicious website (e.g, https://evialhacker.com
) on the Internet from running JS in a browser to read data from a third-party webmail service (e.g., https://mail.example.com
) which the user is signed into or a company intranet which is protected from direct access by the attacker by not having a public IP address and relaying that data to the attacker.
The following table gives examples of origin comparisons with the URL http://store.company.com/dir/page.html
:
URL | Outcome | Reason |
---|---|---|
http://store.company.com/dir2/other.html |
Same origin |
Only the path differs |
http://store.company.com/dir/inner/another.html |
Same origin |
Only the path differs |
https://store.company.com/page.html |
Failure |
Different protocol |
http://store.company.com:81/dir/page.html |
Failure |
Different port (http:// is port 80 by default) |
http://news.company.com/dir/page.html |
Failure |
Different host |
2. Cross-Origin Resource Sharing (CORS)
Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading of resources.
CORS also relies on a mechanism by which browsers make a preflight request by sending headers that indicate the HTTP method and headers that will be used in the actual request to the server hosting the cross-origin resource, in order to check that the server will permit the actual request.
An example of a cross-origin request: the front-end JavaScript code served from https://domain-a.com
uses XMLHttpRequest to make a request for https://domain-b.com/data.json
.
For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts.
-
For example, XMLHttpRequest and the Fetch API follow the same-origin policy.
-
This means that a web application using those APIs can only request resources from the same origin the application was loaded from unless the response from other origins includes the right CORS headers.
The CORS mechanism supports secure cross-origin requests and data transfers between browsers and servers. Modern browsers use CORS in APIs such as XMLHttpRequest
or Fetch
to mitigate the risks of cross-origin HTTP requests.
POST /j/collect?v=1&_v=j93&aip=1&a=533158175&t=pageview&_s=1&dl=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FHTTP%2FCORS&dr=https%3A%2F%2Fwww.google.com%2F&ul=en-us&de=UTF-8&dt=Cross-Origin%20Resource%20Sharing%20(CORS)%20-%20HTTP%20%7C%20MDN&sd=24-bit&sr=1280x720&vp=1263x216&je=0&_u=QACAAAABAAAAAC~&jid=953497891&gjid=135605942&cid=1941250994.1619409894&tid=UA-36116321-5&_gid=137293116.1630465579&_r=1&_slc=1&z=1073481453 HTTP/2
Host: www.google-analytics.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: text/plain (1)
Content-Length: 0
Origin: https://developer.mozilla.org (2)
Connection: keep-alive
Referer: https://developer.mozilla.org/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Pragma: no-cache
Cache-Control: no-cache
TE: trailers
HTTP/2 200 OK
access-control-allow-origin: https://developer.mozilla.org (2)
date: Thu, 02 Sep 2021 03:17:35 GMT
pragma: no-cache
expires: Fri, 01 Jan 1990 00:00:00 GMT
cache-control: no-cache, no-store, must-revalidate
last-modified: Sun, 17 May 1998 03:00:00 GMT
access-control-allow-credentials: true (3)
x-content-type-options: nosniff
content-type: text/plain
cross-origin-resource-policy: cross-origin
server: Golfe2
content-length: 4
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
X-Firefox-Spdy: h2
The Cross-Origin Resource Sharing standard works by adding new HTTP headers that let servers describe which origins are permitted to read that information from a web browser.
-
Additionally, for HTTP request methods that can cause side-effects on server data (in particular, HTTP methods other than GET, or POST with certain MIME types), the specification mandates that browsers "preflight" the request, soliciting supported methods from the server with the HTTP OPTIONS request method, and then, upon "approval" from the server, sending the actual request.
-
Servers can also inform clients whether "credentials" (such as Cookies and HTTP Authentication) should be sent with requests.
CORS failures result in errors, but for security reasons, specifics about the error are not available to JavaScript. All the code knows is that an error occurred. The only way to determine what specifically went wrong is to look at the browser’s console for details. |
2.1. Simple requests
Some requests don’t trigger a CORS preflight which are called simple requests, though the Fetch spec (which defines CORS) doesn’t use that term.
A simple request is one that meets all the following conditions:
-
One of the allowed methods:
-
Apart from the headers automatically set by the user agent (for example, Connection, User-Agent, or the other headers defined in the Fetch spec as a forbidden header name), the only headers which are allowed to be manually set are those which the Fetch spec defines as a CORS-safelisted request-header, which are:
-
Content-Type (but note the additional requirements below)
-
The only allowed values for the Content-Type header are:
-
application/x-www-form-urlencoded
-
multipart/form-data
-
text/plain
-
-
If the request is made using an XMLHttpRequest object, no event listeners are registered on the object returned by the
XMLHttpRequest.upload
property used in the request; that is, given an XMLHttpRequest instancexhr
, no code has calledxhr.upload.addEventListener()
to add an event listener to monitor the upload. -
No ReadableStream object is used in the request.
For example, suppose web content at https://foo.example
wishes to invoke content on domain https://bar.other
. Code of this sort might be used in JavaScript deployed on foo.example
:
const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';
xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();
This performs a simple exchange between the client and the server, using CORS headers to handle the privileges:
Let’s look at what the browser will send to the server in this case, and let’s see how the server responds:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example (1)
1 | The request header of note is Origin, which shows that the invocation is coming from https://foo.example . |
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: * (1)
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]
1 | In response, the server sends back an Access-Control-Allow-Origin header with Access-Control-Allow-Origin: * , which means that the resource can be accessed by any origin.
If the resource owners at
|
2.2. Preflighted requests
Unlike simple requests, for "preflighted" requests the browser first sends an HTTP request using the OPTIONS method to the resource on the other origin, in order to determine if the actual request is safe to send.
The following is an example of a request that will be preflighted to create an XML body to send with the POST request.
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('X-PINGOTHER', 'pingpong'); (1)
xhr.setRequestHeader('Content-Type', 'application/xml'); (2)
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');
1 | Also, a non-standard HTTP X-PINGOTHER request header is set. Such headers are not part of HTTP/1.1, but are generally useful to web applications. |
2 | Since the request uses a Content-Type of application/xml , and since a custom header is set, this request is preflighted. |
Let’s look at the full exchange between client and server. The first exchange is the preflight request/response:
Note: As described below, the actual POST request does not include the Access-Control-Request-* headers; they are needed only for the OPTIONS request.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Lines 1 - 10 above represent the preflight request with the OPTIONS method.
-
The browser determines that it needs to send this based on the request parameters that the JavaScript code snippet above was using, so that the server can respond whether it is acceptable to send the request with the actual request parameters.
-
OPTIONS
is an HTTP/1.1 method that is used to determine further information from servers, and is a safe method, meaning that it can’t be used to change the resource. -
Note that along with the
OPTIONS
request, two other request headers are sent (lines 9 and 10 respectively):Access-Control-Request-Method: POST (1) Access-Control-Request-Headers: X-PINGOTHER, Content-Type (2)
1 The Access-Control-Request-Method header notifies the server as part of a preflight request that when the actual request is sent, it will be sent with a POST
request method.2 The Access-Control-Request-Headers header notifies the server that when the actual request is sent, it will be sent with a X-PINGOTHER
andContent-Type
custom headers. The server now has an opportunity to determine whether it wishes to accept a request under these circumstances.
Lines 13 - 22 above are the response that the server sends back, which indicate that the request method (POST
) and request headers (X-PINGOTHER
) are acceptable. In particular, let’s look at lines 16-19:
Access-Control-Allow-Origin: https://foo.example (1)
Access-Control-Allow-Methods: POST, GET, OPTIONS (2)
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type (3)
Access-Control-Max-Age: 86400 (4)
1 | The server responds with Access-Control-Allow-Origin: https://foo.example , restricting access to just the requesting origin domain. |
2 | It also responds with Access-Control-Allow-Methods, which says that POST and GET are viable methods to query the resource in question (this header is similar to the Allow response header, but used strictly within the context of access control). |
3 | The server also sends Access-Control-Allow-Headers with a value of “X-PINGOTHER, Content-Type”, confirming that these are permitted headers to be used with the actual request.
Like |
4 | Finally, Access-Control-Max-Age gives the value in seconds for how long the response to the preflight request can be cached for without sending another preflight request. In this case, 86400 seconds is 24 hours.
Note that each browser has a maximum internal value that takes precedence when the |
Once the preflight request is complete, the real request is sent:
POST /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache
<person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some XML payload]
2.3. Requests with credentials
The most interesting capability exposed by both XMLHttpRequest or Fetch and CORS is the ability to make "credentialed" requests that are aware of HTTP cookies and HTTP Authentication information.
-
By default, in cross-site XMLHttpRequest or Fetch invocations, browsers will not send credentials.
-
A specific flag has to be set on the
XMLHttpRequest
object or theRequest
constructor when it is invoked.
In this example, content originally loaded from https://foo.example
makes a simple GET request to a resource on https://bar.other
which sets Cookies. Content on foo.example
might contain JavaScript like this:
1
2
3
4
5
6
7
8
9
10
11
const invocation = new XMLHttpRequest();
const url = 'https://bar.other/resources/credentialed-content/';
function callOtherDomain() {
if (invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true; (1)
invocation.onreadystatechange = handler;
invocation.send();
}
}
1 | Line 7 shows the flag on XMLHttpRequest that has to be set in order to make the invocation with Cookies, namely the withCredentials boolean value. By default, the invocation is made without Cookies.
Since this is a simple |
Here is a sample exchange between client and server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
GET /resources/credentialed-content/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: https://foo.example/examples/credential.html
Origin: https://foo.example
Cookie: pageAccess=2 (1)
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
[text/plain payload]
1 | Although line 10 contains the Cookie destined for the content on https://bar.other , if bar.other did not respond with an Access-Control-Allow-Credentials: true (line 17) the response would be ignored and not made available to web content. |
CORS-preflight requests must never include credentials. The response to a preflight request must specify Access-Control-Allow-Credentials: true to indicate that the actual request can be made with credentials.
|
2.3.1. Credentialed requests and wildcards
When responding to a credentialed request:
-
The server MUST NOT specify the “*” wildcard for the
Access-Control-Allow-Origin
response-header value, but must instead specify an explicit origin; for example:Access-Control-Allow-Origin: https://example.com
-
The server MUST NOT specify the “*” wildcard for the
Access-Control-Allow-Headers
response-header value, but must instead specify an explicit list of header names; for example,Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
-
The server MUST NOT specify the “*” wildcard for the
Access-Control-Allow-Methods
response-header value, but must instead specify an explicit list of method names; for example,Access-Control-Allow-Methods: POST, GET
If a request includes a credential (most commonly a Cookie header) and the response includes an But if a request includes a credential (like the Also note that any |
2.3.2. Third-party cookies
Note that cookies set in CORS responses are subject to normal third-party cookie policies.
In the example above, the page is loaded from foo.example
, but the cookie on line 20 is sent by bar.other
, and would thus not be saved if the user has configured their browser to reject all third-party cookies.
Cookie in the request (line 10) may also be suppressed in normal third-party cookie policies. The enforced cookie policy may therefore nullify the capability described in this chapter, effectively prevents you from making credentialed requests whatsoever.
Cookie policy around the SameSite attribute would apply.
2.4. The HTTP response headers
-
Access-Control-Allow-Origin: <origin> | *
Access-Control-Allow-Origin
specifies either a single origin, which tells browsers to allow that origin to access the resource; or else — for requests without credentials — the “*” wildcard, to tell browsers to allow any origin to access the resource.For example, to allow code from the origin
https://mozilla.org
to access the resource, you can specify:Access-Control-Allow-Origin: https://mozilla.org Vary: Origin
CORS and cachingIf the server sends a response with an
Access-Control-Allow-Origin
value that is an explicit origin (rather than the “*” wildcard), then the response should also include a Vary response header with the valueOrigin
— to indicate to browsers that server responses can differ based on the value of theOrigin
request header. -
Access-Control-Expose-Headers: <header-name>[, <header-name>]*
The
Access-Control-Expose-Headers
header adds the specified headers to the allowlist that JavaScript (such asgetResponseHeader()
) in browsers is allowed to access.For example, the following:
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
…would allow the
X-My-Custom-Header
andX-Another-Custom-Header
headers to be exposed to the browser. -
Access-Control-Max-Age: <delta-seconds>
The
Access-Control-Max-Age
header indicates how long the results of a preflight request can be cached.The
delta-seconds
parameter indicates the number of seconds the results can be cached. -
Access-Control-Allow-Credentials: true
The
Access-Control-Allow-Credentials
header indicates whether or not the response to the request can be exposed when the credentials flag istrue
.When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials.
Note that simple
GET
requests are not preflighted, and so if a request is made for a resource with credentials, if this header is not returned with the resource, the response is ignored by the browser and not returned to web content. -
Access-Control-Allow-Methods: <method>[, <method>]*
The
Access-Control-Allow-Methods
header specifies the method or methods allowed when accessing the resource. -
Access-Control-Allow-Headers: <header-name>[, <header-name>]*
The
Access-Control-Allow-Headers
header is used in response to a preflight request to indicate which HTTP headers can be used when making the actual request.
2.5. The HTTP request headers
-
Origin: <origin>
The
Origin
header indicates the origin of the cross-site access request or preflight request.The origin is a URL indicating the server from which the request initiated. It does not include any path information, but only the server name.
Note: The origin value can be null.
Note that in any access control request, the
Origin
header is always sent. -
Access-Control-Request-Method: <method>
The
Access-Control-Request-Method
is used when issuing a preflight request to let the server know what HTTP method will be used when the actual request is made. -
Access-Control-Request-Headers: <field-name>[, <field-name>]*
The
Access-Control-Request-Headers
header is used when issuing a preflight request to let the server know what HTTP headers will be used when the actual request is made (such as withsetRequestHeader()
).
3. Domain Name System (DNS) and Public Suffix List (PSL)
A domain name is an identification string that defines a realm of administrative autonomy, authority or control within the Internet.
-
Domain names are used in various networking contexts and for application-specific naming and addressing purposes.
-
In general, a domain name identifies a network domain, or it represents an Internet Protocol (IP) resource, such as a personal computer used to access the Internet, a server computer hosting a website, or the web site itself or any other service communicated via the Internet.
Domain names are formed by the rules and procedures of the Domain Name System (DNS). Any name registered in the DNS is a domain name.
Domain names are organized in subordinate levels (subdomains) of the DNS root domain, which is nameless (.
).
-
The first-level set of domain names are the top-level domains (TLDs), including the generic top-level domains (gTLDs), such as the prominent domains
com
,info
,net
,edu
, andorg
, and the country code top-level domains (ccTLDs). -
Below these top-level domains in the DNS hierarchy are the second-level and third-level domain names that are typically open for reservation by end-users who wish to connect local area networks to the Internet, create other publicly accessible Internet resources or run web sites.
A fully qualified domain name (FQDN) is a domain name that is completely specified with all labels in the hierarchy of the DNS, having no parts omitted.
-
Traditionally a FQDN ends in a dot (.) to denote the top of the DNS tree.
-
Labels in the Domain Name System are case-insensitive, and may therefore be written in any desired capitalization method, but most commonly domain names are written in lowercase in technical contexts.
-
In contrast to a domain name that is fully specified, a domain name that does not include the full path of labels up to the DNS root is often called a partially qualified domain name.
The Public Suffix List (PSL) is a catalog of certain Internet domain names. Entries on the list are also referred to as effective top-level domains (eTLD).
Previously, browsers used an algorithm which basically only denied setting wide-ranging cookies for top-level domains (TLD) with no dots (e.g. com
or org
). However, this did not work for top-level domains where only third-level registrations are allowed (e.g. co.uk
). In these cases, websites could set a cookie for co.uk
which will be passed onto every website registered under co.uk
.
Clearly, this was a security risk as it allowed websites other than the one setting the cookie to read it, and therefore potentially extract sensitive information.
Since there is no algorithmic method of finding the highest level at which a domain may be registered for a particular top-level domain (the policies differ with each registry), the only method is to create a list of all top-level domains and the level at which domains can be registered. This is the aim of the effective TLD list.
4. Same-Site
The site of a piece of web content is determined by the registrable domain of the host within the origin. This is computed by consulting a Public Suffix List to find the portion of the host which is counted as the public suffix (e.g. com
, org
or co.uk
).
The concept of a site is used in SameSite
cookies, as well as a web application’s Cross-Origin Resource Policy
.
https://developer.mozilla.org/en-US/docs/ https://support.mozilla.org/en-US/ |
same site because the registrable domain of mozilla.org is the same |
http://example.com:8080 https://example.com |
same site because scheme and port are not relevant |
https://developer.mozilla.org/en-US/docs/ https://example.com |
not same site because the registrable domain of the two URLs differs |
https://example.co.uk https://example.uk |
4.1. Same-Site and Cross-Site Requests
A request is "same-site" if its target’s URI’s origin’s registrable domain is an exact match for the request’s initiator’s "site for cookies", and "cross-site" otherwise. To be more precise, for a given request ("request"), the following algorithm returns "same-site" or "cross-site":
-
If "request"'s client is "null", return "same-site".
If the request doesn’t originate from a client (e.g., a server-side request), it’s considered same-site.
-
Let "site" be "request"'s client’s "site for cookies".
-
Let "target" be the registrable domain of "request"'s current url.
-
If "site" is an exact match for "target", return "same-site".
For example, if a script on
https://example.com/page1.html
makes a request tohttps://example.com/api/data
, it’s a same-site request. -
Return "cross-site".
For example, if a script on
https://example.com/page1.html
makes a request tohttps://api.anotherdomain.com/data
orhttps://sub.example.com/sub-page.html
, it’s a cross-site request.
4.2. Same-Site cookies
The SameSite
attribute of the Set-Cookie HTTP response header allows you to declare if your cookie should be restricted to a first-party or same-site context.
Note: Standards related to the Cookie The cookie-sending behavior if Cookies with |
The SameSite
attribute accepts three values:
-
Lax
Cookies are not sent on normal cross-site subrequests (for example to load images or frames into a third party site), but are sent when a user is navigating to the origin site (i.e., when following a link).
This is the default cookie value if
SameSite
has not been explicitly specified in recent browser versions (Defaults toLax
).Note:
Lax
replacedNone
as the default value in order to ensure that users have reasonably robust defense against some classes of cross-site request forgery (CSRF) attacks. -
Strict
Cookies will only be sent in a first-party context and not be sent along with requests initiated by third party websites.
-
None
Cookies will be sent in all contexts, i.e. in responses to both first-party and cross-origin requests.
If SameSite=None
is set, the cookieSecure
attribute must also be set (or the cookie will be blocked).
4.3. Third-party cookies
A cookie is associated with a domain.
-
If this domain is the same as the domain of the page you are on, the cookie is called a first-party cookie.
-
If the domain is different, it is a third-party cookie.
While the server hosting a web page sets first-party cookies, the page may contain images or other components stored on servers in other domains (for example, ad banners), which may set third-party cookies.
These are mainly used for advertising and tracking across the web.
A third-party server can build up a profile of a user’s browsing history and habits based on cookies sent to it by the same browser when accessing multiple sites.
-
Firefox, by default, blocks third-party cookies that are known to contain trackers.
-
Third-party cookies (or just tracking cookies) may also be blocked by other browser settings or extensions.
-
Cookie blocking can cause some third-party components (such as social media widgets) to not function as intended.
5. Cross-Site Request Forgery (CSRF)
CSRF (sometimes also called XSRF) is a related class of attack. The attacker causes the user’s browser to perform a request to the website’s backend without the user’s consent or knowledge. An attacker can use an XSS payload to launch a CSRF attack.
Wikipedia mentions a good example for CSRF. In this situation, someone includes an image that isn’t really an image (for example in an unfiltered chat or forum), instead it really is a request to your bank’s server to withdraw money:
<img src="https://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
Now, if you are logged into your bank account and your cookies are still valid (and there is no other validation), you will transfer money as soon as you load the HTML that contains this image. For endpoints that require a POST
request, it’s possible to programmatically trigger a <form>
submit (perhaps in an invisible <iframe>
) when the page is loaded:
<form action="https://bank.example.com/withdraw" method="POST">
<input type="hidden" name="account" value="bob">
<input type="hidden" name="amount" value="1000000">
<input type="hidden" name="for" value="mallory">
</form>
<script>window.addEventListener('DOMContentLoaded', (e) => { document.querySelector('form').submit(); }</script>
There are a few techniques that should be used to prevent this from happening:
-
GET
endpoints should be idempotent—actions that enact a change and do not retrieve data should require sending aPOST
(or other HTTP method) request.POST
endpoints should not interchangeably acceptGET
requests with parameters in the query string. -
A CSRF token should be included in
<form>
elements via a hidden input field. This token should be unique per user and stored (for example, in a cookie) such that the server can look up the expected value when the request is sent. For all non-GET requests that have the potential to perform an action, this input field should be compared against the expected value. If there is a mismatch, the request should be aborted. -
This method of protection relies on an attacker being unable to predict the user’s assigned CSRF token. The token should be regenerated on sign-in.
-
Cookies that are used for sensitive actions (such as session cookies) should have a short lifetime with the
SameSite
attribute set toStrict
orLax
. In supporting browsers, this will have the effect of ensuring that the session cookie is not sent along with cross-site requests and so the request is effectively unauthenticated to the application server. -
Both CSRF tokens and SameSite cookies should be deployed. This ensures all browsers are protected and provides protection where SameSite cookies cannot help (such as attacks originating from a separate subdomain).
6. References
-
https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
-
https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks
-
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
-
https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-03
-
https://datatracker.ietf.org/doc/html/draft-west-first-party-cookies-07